diff options
Diffstat (limited to 'src/plugins/platforms/windows')
63 files changed, 1901 insertions, 884 deletions
diff --git a/src/plugins/platforms/windows/CMakeLists.txt b/src/plugins/platforms/windows/CMakeLists.txt index 4425e78b30..4b92317978 100644 --- a/src/plugins/platforms/windows/CMakeLists.txt +++ b/src/plugins/platforms/windows/CMakeLists.txt @@ -15,13 +15,13 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin qwin10helpers.cpp qwin10helpers.h qwindowsapplication.cpp qwindowsapplication.h qwindowsbackingstore.cpp qwindowsbackingstore.h - qwindowscombase.h qwindowscontext.cpp qwindowscontext.h qwindowscursor.cpp qwindowscursor.h qwindowsdialoghelpers.cpp qwindowsdialoghelpers.h qwindowsdropdataobject.cpp qwindowsdropdataobject.h qwindowsgdiintegration.cpp qwindowsgdiintegration.h qwindowsgdinativeinterface.cpp qwindowsgdinativeinterface.h + qwindowsiconengine.cpp qwindowsiconengine.h qwindowsinputcontext.cpp qwindowsinputcontext.h qwindowsintegration.cpp qwindowsintegration.h qwindowsinternalmimedata.cpp qwindowsinternalmimedata.h @@ -38,6 +38,8 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin qwindowstheme.cpp qwindowstheme.h qwindowsthreadpoolrunner.h qwindowswindow.cpp qwindowswindow.h + NO_UNITY_BUILD_SOURCES + qwindowspointerhandler.cpp DEFINES QT_NO_CAST_FROM_ASCII QT_NO_FOREACH @@ -54,6 +56,7 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin imm32 ole32 oleaut32 + setupapi shell32 shlwapi user32 @@ -66,10 +69,6 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin runtimeobject ) -# Duplicated symbols -set_source_files_properties(qwindowspointerhandler.cpp qwindowsmousehandler.cpp - PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) - # Resources: set_source_files_properties("openglblacklists/default.json" PROPERTIES QT_RESOURCE_ALIAS "default.json" @@ -104,6 +103,8 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW LIBRARIES uuid + NO_PCH_SOURCES + qwindowspointerhandler.cpp ) qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_systemtrayicon @@ -174,6 +175,7 @@ endif() qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_accessibility SOURCES + uiautomation/qwindowsuiautomation.cpp uiautomation/qwindowsuiautomation.h uiautomation/qwindowsuiaaccessibility.cpp uiautomation/qwindowsuiaaccessibility.h uiautomation/qwindowsuiabaseprovider.cpp uiautomation/qwindowsuiabaseprovider.h uiautomation/qwindowsuiaexpandcollapseprovider.cpp uiautomation/qwindowsuiaexpandcollapseprovider.h @@ -195,11 +197,17 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_accessi uiautomation/qwindowsuiawindowprovider.cpp uiautomation/qwindowsuiawindowprovider.h ) +if(QT_FEATURE_accessibility) + find_library(UI_AUTOMATION_LIBRARY uiautomationcore) + if(UI_AUTOMATION_LIBRARY) + qt_internal_extend_target(QWindowsIntegrationPlugin + LIBRARIES + ${UI_AUTOMATION_LIBRARY} + ) + endif() +endif() + qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW AND QT_FEATURE_accessibility LIBRARIES uuid ) - -if (MINGW) - set_source_files_properties(qwindowspointerhandler.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON) -endif() diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h index 22787bd63e..96a72600eb 100644 --- a/src/plugins/platforms/windows/qtwindowsglobal.h +++ b/src/plugins/platforms/windows/qtwindowsglobal.h @@ -119,7 +119,7 @@ enum WindowsEventType // Simplify event types NonClientPointerEvent = NonClientEventFlag + PointerEventFlag + 4, KeyEvent = KeyEventFlag + 1, KeyDownEvent = KeyEventFlag + KeyDownEventFlag + 1, - KeyboardLayoutChangeEvent = KeyEventFlag + 2, + InputLanguageChangeEvent = KeyEventFlag + 2, InputMethodKeyEvent = InputMethodEventFlag + KeyEventFlag + 1, InputMethodKeyDownEvent = InputMethodEventFlag + KeyEventFlag + KeyDownEventFlag + 1, ClipboardEvent = ClipboardEventFlag + 1, @@ -230,7 +230,7 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI return QtWindows::InputMethodKeyDownEvent; #ifdef WM_INPUTLANGCHANGE case WM_INPUTLANGCHANGE: - return QtWindows::KeyboardLayoutChangeEvent; + return QtWindows::InputLanguageChangeEvent; #endif // WM_INPUTLANGCHANGE case WM_TOUCH: return QtWindows::TouchEvent; diff --git a/src/plugins/platforms/windows/qwindowsapplication.cpp b/src/plugins/platforms/windows/qwindowsapplication.cpp index 00477e2890..42e34ac99f 100644 --- a/src/plugins/platforms/windows/qwindowsapplication.cpp +++ b/src/plugins/platforms/windows/qwindowsapplication.cpp @@ -72,11 +72,6 @@ bool QWindowsApplication::setWinTabEnabled(bool enabled) return enabled ? ctx->initTablet() : ctx->disposeTablet(); } -bool QWindowsApplication::isDarkMode() const -{ - return QWindowsContext::isDarkMode(); -} - QWindowsApplication::DarkModeHandling QWindowsApplication::darkModeHandling() const { return m_darkModeHandling; @@ -143,9 +138,9 @@ QVariant QWindowsApplication::gpuList() const return result; } -void QWindowsApplication::lightSystemPalette(QPalette &result) const +void QWindowsApplication::populateLightSystemPalette(QPalette &result) const { - QWindowsTheme::populateLightSystemBasePalette(result); + result = QWindowsTheme::systemPalette(Qt::ColorScheme::Light); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsapplication.h b/src/plugins/platforms/windows/qwindowsapplication.h index 8b74b47f3d..0918df91af 100644 --- a/src/plugins/platforms/windows/qwindowsapplication.h +++ b/src/plugins/platforms/windows/qwindowsapplication.h @@ -24,7 +24,6 @@ public: bool isWinTabEnabled() const override; bool setWinTabEnabled(bool enabled) override; - bool isDarkMode() const override; DarkModeHandling darkModeHandling() const override; void setDarkModeHandling(DarkModeHandling handling) override; @@ -43,7 +42,7 @@ public: QVariant gpu() const override; QVariant gpuList() const override; - void lightSystemPalette(QPalette &palette) const override; + void populateLightSystemPalette(QPalette &palette) const override; private: WindowActivationBehavior m_windowActivationBehavior = DefaultActivateWindow; diff --git a/src/plugins/platforms/windows/qwindowsbackingstore.cpp b/src/plugins/platforms/windows/qwindowsbackingstore.cpp index 0f9d0172d9..07918f6094 100644 --- a/src/plugins/platforms/windows/qwindowsbackingstore.cpp +++ b/src/plugins/platforms/windows/qwindowsbackingstore.cpp @@ -117,7 +117,7 @@ void QWindowsBackingStore::resize(const QSize &size, const QRegion ®ion) if (QImage::toPixelFormat(format).alphaUsage() == QPixelFormat::UsesAlpha) m_alphaNeedsFill = true; else // upgrade but here we know app painting does not rely on alpha hence no need to fill - format = qt_maybeAlphaVersionWithSameDepth(format); + format = qt_maybeDataCompatibleAlphaVersion(format); QWindowsNativeImage *oldwni = m_image.data(); auto *newwni = new QWindowsNativeImage(size.width(), size.height(), format); diff --git a/src/plugins/platforms/windows/qwindowscombase.h b/src/plugins/platforms/windows/qwindowscombase.h deleted file mode 100644 index 04d4dc51cf..0000000000 --- a/src/plugins/platforms/windows/qwindowscombase.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef QWINDOWSCOMBASE_H -#define QWINDOWSCOMBASE_H - -#include <QtCore/qglobal.h> - -#include <unknwn.h> - -QT_BEGIN_NAMESPACE - -// The __uuidof operator of MinGW does not work for all interfaces (for example, -// IAccessible2). Specializations of this function can be provides to work -// around this. -template <class DesiredInterface> -static IID qUuidOf() { return __uuidof(DesiredInterface); } - -// Helper for implementing IUnknown::QueryInterface. -template <class DesiredInterface, class Derived> -bool qWindowsComQueryInterface(Derived *d, REFIID id, LPVOID *iface) -{ - if (id == qUuidOf<DesiredInterface>()) { - *iface = static_cast<DesiredInterface *>(d); - d->AddRef(); - return true; - } - return false; -} - -// Helper for implementing IUnknown::QueryInterface for IUnknown -// in the case of multiple inheritance via the first inherited class. -template <class FirstInheritedInterface, class Derived> -bool qWindowsComQueryUnknownInterfaceMulti(Derived *d, REFIID id, LPVOID *iface) -{ - if (id == __uuidof(IUnknown)) { - *iface = static_cast<FirstInheritedInterface *>(d); - d->AddRef(); - return true; - } - return false; -} - -// Helper base class to provide IUnknown methods for COM classes (single inheritance) -template <class ComInterface> class QWindowsComBase : public ComInterface -{ - Q_DISABLE_COPY_MOVE(QWindowsComBase) -public: - explicit QWindowsComBase(ULONG initialRefCount = 1) : m_ref(initialRefCount) {} - virtual ~QWindowsComBase() = default; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override - { - *iface = nullptr; - return qWindowsComQueryInterface<IUnknown>(this, id, iface) || qWindowsComQueryInterface<ComInterface>(this, id, iface) - ? S_OK : E_NOINTERFACE; - } - - ULONG STDMETHODCALLTYPE AddRef() override { return ++m_ref; } - - ULONG STDMETHODCALLTYPE Release() override - { - if (!--m_ref) { - delete this; - return 0; - } - return m_ref; - } - -private: - ULONG m_ref; -}; - -// Clang does not consider __declspec(nothrow) as nothrow -QT_WARNING_DISABLE_CLANG("-Wmicrosoft-exception-spec") -QT_WARNING_DISABLE_CLANG("-Wmissing-exception-spec") - -QT_END_NAMESPACE - -#endif // QWINDOWSCOMBASE_H diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 5475e9707b..de65a2171d 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -45,10 +45,13 @@ #include <QtCore/qscopedpointer.h> #include <QtCore/quuid.h> #include <QtCore/private/qwinregistry_p.h> -#include <QtCore/private/qfactorycacheregistration_p.h> +#if QT_CONFIG(cpp_winrt) +# include <QtCore/private/qfactorycacheregistration_p.h> +#endif #include <QtCore/private/qsystemerror_p.h> #include <QtGui/private/qwindowsguieventdispatcher_p.h> +#include <QtGui/private/qwindowsthemecache_p.h> #include <stdlib.h> #include <stdio.h> @@ -151,11 +154,9 @@ struct QWindowsContextPrivate { bool m_asyncExpose = false; HPOWERNOTIFY m_powerNotification = nullptr; HWND m_powerDummyWindow = nullptr; - static bool m_darkMode; static bool m_v2DpiAware; }; -bool QWindowsContextPrivate::m_darkMode = false; bool QWindowsContextPrivate::m_v2DpiAware = false; QWindowsContextPrivate::QWindowsContextPrivate() @@ -169,7 +170,6 @@ QWindowsContextPrivate::QWindowsContextPrivate() m_systemInfo |= QWindowsContext::SI_RTL_Extensions; m_keyMapper.setUseRTLExtensions(true); } - m_darkMode = QWindowsTheme::queryDarkMode(); if (FAILED(m_oleInitializeResult)) { qWarning() << "QWindowsContext: OleInitialize() failed: " << QSystemError::windowsComString(m_oleInitializeResult); @@ -461,11 +461,6 @@ bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwarenes return true; } -bool QWindowsContext::isDarkMode() -{ - return QWindowsContextPrivate::m_darkMode; -} - QWindowsContext *QWindowsContext::instance() { return m_instance; @@ -481,9 +476,9 @@ bool QWindowsContext::useRTLExtensions() const return d->m_keyMapper.useRTLExtensions(); } -QList<int> QWindowsContext::possibleKeys(const QKeyEvent *e) const +QPlatformKeyMapper *QWindowsContext::keyMapper() const { - return d->m_keyMapper.possibleKeys(e); + return &d->m_keyMapper; } QWindowsContext::HandleBaseWindowHash &QWindowsContext::windows() @@ -535,6 +530,8 @@ QString QWindowsContext::classNamePrefix() # define xstr(s) str(s) # define str(s) #s str << xstr(QT_NAMESPACE); +# undef str +# undef xstr #endif } return result; @@ -1001,9 +998,10 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, MSG msg; msg.hwnd = hwnd; // re-create MSG structure - msg.message = message; // time and pt fields ignored + msg.message = message; msg.wParam = wParam; msg.lParam = lParam; + msg.time = GetMessageTime(); msg.pt.x = msg.pt.y = 0; if (et != QtWindows::CursorEvent && (et & (QtWindows::MouseEventFlag | QtWindows::NonClientEventFlag))) { msg.pt.x = GET_X_LPARAM(lParam); @@ -1087,21 +1085,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, // Only refresh the window theme if the user changes the personalize settings. if ((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL. && (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0)) { - const bool darkMode = QWindowsTheme::queryDarkMode(); - const bool darkModeChanged = darkMode != QWindowsContextPrivate::m_darkMode; - QWindowsContextPrivate::m_darkMode = darkMode; - auto integration = QWindowsIntegration::instance(); - integration->updateApplicationBadge(); - if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) { - QWindowsTheme::instance()->refresh(); - QWindowSystemInterface::handleThemeChange(); - } - if (darkModeChanged) { - if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) { - for (QWindowsWindow *w : d->m_windows) - w->setDarkBorder(QWindowsContextPrivate::m_darkMode); - } - } + QWindowsTheme::handleSettingsChanged(); } return d->m_screenManager.handleScreenChanges(); } @@ -1118,7 +1102,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, 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)); + d->m_creationContext->obtainedSize = QSize(LOWORD(lParam), HIWORD(lParam)); return true; case QtWindows::MoveEvent: d->m_creationContext->obtainedPos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); @@ -1161,7 +1145,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, if (wParam == DBT_DEVNODES_CHANGED) initTouch(); break; - case QtWindows::KeyboardLayoutChangeEvent: + case QtWindows::InputLanguageChangeEvent: if (QWindowsInputContext *wic = windowsInputContext()) wic->handleInputLanguageChanged(wParam, lParam); Q_FALLTHROUGH(); @@ -1205,21 +1189,29 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::ExposeEvent: return platformWindow->handleWmPaint(hwnd, message, wParam, lParam, result); case QtWindows::NonClientMouseEvent: - if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled()) + 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 ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled()) + 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)) @@ -1265,6 +1257,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, QWindowSystemInterface::handleCloseEvent(platformWindow->window()); return true; case QtWindows::ThemeChanged: { + QWindowsThemeCache::clearThemeCache(platformWindow->handle()); // Switch from Aero to Classic changes margins. if (QWindowsTheme *theme = QWindowsTheme::instance()) theme->windowsThemeChanged(platformWindow->window()); @@ -1403,7 +1396,7 @@ void QWindowsContext::handleFocusEvent(QtWindows::WindowsEventType et, } if (nextActiveWindow != d->m_lastActiveWindow) { d->m_lastActiveWindow = nextActiveWindow; - QWindowSystemInterface::handleWindowActivated(nextActiveWindow, Qt::ActiveWindowFocusReason); + QWindowSystemInterface::handleFocusWindowChanged(nextActiveWindow, Qt::ActiveWindowFocusReason); } } @@ -1433,7 +1426,7 @@ bool QWindowsContext::handleContextMenuEvent(QWindow *window, const MSG &msg) } QWindowSystemInterface::handleContextMenuEvent(window, mouseTriggered, pos, globalPos, - QWindowsKeyMapper::queryKeyboardModifiers()); + keyMapper()->queryKeyboardModifiers()); return true; } #endif @@ -1454,7 +1447,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window) const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons(); if (currentButtons == appButtons) return; - const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const Qt::KeyboardModifiers keyboardModifiers = keyMapper()->queryKeyboardModifiers(); const QPoint globalPos = QWindowsCursor::mousePosition(); const QPlatformWindow *platWin = window->handle(); const QPoint localPos = platWin->mapFromGlobal(globalPos); @@ -1463,8 +1456,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window) for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) { if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) { QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, - currentButtons, button, type, - keyboardModifiers); + currentButtons, button, type, keyboardModifiers); } } if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 1a3b47be5e..0539a22afc 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -33,6 +33,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) class QWindow; class QPlatformScreen; class QPlatformWindow; +class QPlatformKeyMapper; class QWindowsMenuBar; class QWindowsScreenManager; class QWindowsTabletSupport; @@ -43,7 +44,6 @@ struct QWindowsContextPrivate; class QPoint; class QKeyEvent; class QPointingDevice; - class QWindowsContext { Q_DISABLE_COPY_MOVE(QWindowsContext) @@ -120,15 +120,13 @@ public: 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; + QPlatformKeyMapper *keyMapper() const; HandleBaseWindowHash &windows(); diff --git a/src/plugins/platforms/windows/qwindowscursor.cpp b/src/plugins/platforms/windows/qwindowscursor.cpp index 77903f6b6f..b416886120 100644 --- a/src/plugins/platforms/windows/qwindowscursor.cpp +++ b/src/plugins/platforms/windows/qwindowscursor.cpp @@ -30,6 +30,8 @@ static bool initResources() QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + /*! \class QWindowsCursorCacheKey \brief Cache key for storing values in a QHash with a QCursor as key. @@ -103,14 +105,16 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits, hotSpot.setX(width / 2); if (hotSpot.y() < 0) hotSpot.setY(height / 2); - const int n = qMax(1, width / 8); - QScopedArrayPointer<uchar> xBits(new uchar[height * n]); - QScopedArrayPointer<uchar> xMask(new uchar[height * n]); + // a ddb is word aligned, QImage depends on bow it was created + const auto bplDdb = qMax(1, ((width + 15) >> 4) << 1); + const auto bplImg = int(bbits.bytesPerLine()); + QScopedArrayPointer<uchar> xBits(new uchar[height * bplDdb]); + QScopedArrayPointer<uchar> xMask(new uchar[height * bplDdb]); int x = 0; for (int i = 0; i < height; ++i) { const uchar *bits = bbits.constScanLine(i); const uchar *mask = mbits.constScanLine(i); - for (int j = 0; j < n; ++j) { + for (int j = 0; j < bplImg && j < bplDdb; ++j) { uchar b = bits[j]; uchar m = mask[j]; if (invb) @@ -121,6 +125,11 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits, xMask[x] = b ^ m; ++x; } + for (int i = bplImg; i < bplDdb; ++i) { + xBits[x] = 0; + xMask[x] = 0; + ++x; + } } return CreateCursor(GetModuleHandle(nullptr), hotSpot.x(), hotSpot.y(), width, height, xBits.data(), xMask.data()); @@ -138,8 +147,8 @@ static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor = 1) bbits = bbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); mbits = mbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } - bbits = bbits.convertToFormat(QImage::Format_Mono); - mbits = mbits.convertToFormat(QImage::Format_Mono); + bbits = std::move(bbits).convertToFormat(QImage::Format_Mono); + mbits = std::move(mbits).convertToFormat(QImage::Format_Mono); const bool invb = bbits.colorCount() > 1 && qGray(bbits.color(0)) < qGray(bbits.color(1)); const bool invm = mbits.colorCount() > 1 && qGray(mbits.color(0)) < qGray(mbits.color(1)); return createBitmapCursor(bbits, mbits, cursor.hotSpot(), invb, invm); @@ -436,8 +445,8 @@ QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursor if (!bestFit) return PixmapCursor(); - const QPixmap rawImage(QStringLiteral(":/qt-project.org/windows/cursors/images/") + - QString::fromLatin1(bestFit->fileName)); + const QPixmap rawImage(":/qt-project.org/windows/cursors/images/"_L1 + + QLatin1StringView(bestFit->fileName)); return PixmapCursor(rawImage, QPoint(bestFit->hotSpotX, bestFit->hotSpotY)); } #endif // !QT_NO_IMAGEFORMAT_PNG diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index 51bd9f0c38..0ce0b0e2a7 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -4,7 +4,6 @@ #define QT_NO_URL_CAST_FROM_STRING 1 #include <QtCore/qt_windows.h> -#include "qwindowscombase.h" #include "qwindowsdialoghelpers.h" #include "qwindowscontext.h" @@ -33,6 +32,7 @@ #include <QtCore/qtemporaryfile.h> #include <QtCore/private/qfunctions_win_p.h> #include <QtCore/private/qsystemerror_p.h> +#include <QtCore/private/qcomobject_p.h> #include <algorithm> #include <vector> @@ -43,26 +43,6 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -#ifndef QT_NO_DEBUG_STREAM -/* Output UID (IID, CLSID) as C++ constants. - * The constants are contained in the Windows SDK libs, but not for MinGW. */ -static inline QString guidToString(const GUID &g) -{ - QString rc; - QTextStream str(&rc); - str.setIntegerBase(16); - str.setNumberFlags(str.numberFlags() | QTextStream::ShowBase); - str << '{' << g.Data1 << ", " << g.Data2 << ", " << g.Data3; - str.setFieldWidth(2); - str.setFieldAlignment(QTextStream::AlignRight); - str.setPadChar(u'0'); - str << ",{" << g.Data4[0] << ", " << g.Data4[1] << ", " << g.Data4[2] << ", " << g.Data4[3] - << ", " << g.Data4[4] << ", " << g.Data4[5] << ", " << g.Data4[6] << ", " << g.Data4[7] - << "}};"; - return rc; -} -#endif // !QT_NO_DEBUG_STREAM - // Return an allocated wchar_t array from a QString, reserve more memory if desired. static wchar_t *qStringToWCharArray(const QString &s, size_t reserveSize = 0) { @@ -443,7 +423,7 @@ inline void QWindowsFileDialogSharedData::fromOptions(const QSharedPointer<QFile class QWindowsNativeFileDialogBase; -class QWindowsNativeFileDialogEventHandler : public QWindowsComBase<IFileDialogEvents> +class QWindowsNativeFileDialogEventHandler : public QComObject<IFileDialogEvents> { Q_DISABLE_COPY_MOVE(QWindowsNativeFileDialogEventHandler) public: @@ -586,7 +566,7 @@ QUrl QWindowsShellItem::url() const if (urlV.isValid()) return urlV; // Last resort: encode the absolute desktop parsing id as data URL - const QString data = QStringLiteral("data:text/plain;base64,") + const QString data = "data:text/plain;base64,"_L1 + QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64()); return QUrl(data); } @@ -1379,7 +1359,7 @@ QString tempFilePattern(QString name) int lastDot = name.lastIndexOf(u'.'); if (lastDot < 0) lastDot = name.size(); - name.insert(lastDot, QStringLiteral("_XXXXXX")); + name.insert(lastDot, "_XXXXXX"_L1); for (int i = lastDot - 1; i >= 0; --i) { if (!validFileNameCharacter(name.at(i))) @@ -1571,7 +1551,7 @@ QWindowsNativeDialogBase *QWindowsFileDialogHelper::createNativeDialog() if (!info.isDir()) result->selectFile(info.fileName()); } else { - result->selectFile(url.path()); // TODO url.fileName() once it exists + result->selectFile(url.fileName()); } } // No need to select initialNameFilter if mode is Dir @@ -1605,7 +1585,7 @@ void QWindowsFileDialogHelper::selectFile(const QUrl &fileName) qCDebug(lcQpaDialogs) << __FUNCTION__ << fileName.toString(); if (hasNativeDialog()) // Might be invoked from the QFileDialog constructor. - nativeFileDialog()->selectFile(fileName.toLocalFile()); // ## should use QUrl::fileName() once it exists + nativeFileDialog()->selectFile(fileName.fileName()); } QList<QUrl> QWindowsFileDialogHelper::selectedFiles() const @@ -1713,14 +1693,6 @@ static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UIN return dialog->existingDirCallback(hwnd, uMsg, lParam); } -/* The correct declaration of the SHGetPathFromIDList symbol is - * being used in mingw-w64 as of r6215, which is a v3 snapshot. */ -#if defined(Q_CC_MINGW) && (!defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 3) -typedef ITEMIDLIST *qt_LpItemIdList; -#else -using qt_LpItemIdList = PIDLIST_ABSOLUTE; -#endif - int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam) { switch (uMsg) { @@ -1734,7 +1706,7 @@ int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM break; case BFFM_SELCHANGED: { wchar_t path[MAX_PATH]; - const bool ok = SHGetPathFromIDList(reinterpret_cast<qt_LpItemIdList>(lParam), path) + const bool ok = SHGetPathFromIDList(reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path) && path[0]; SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1); } @@ -1756,7 +1728,7 @@ QList<QUrl> QWindowsXpNativeFileDialog::execExistingDir(HWND owner) bi.lpfn = xpFileDialogGetExistingDirCallbackProc; bi.lParam = LPARAM(this); QList<QUrl> selectedFiles; - if (qt_LpItemIdList pItemIDList = SHBrowseForFolder(&bi)) { + if (const auto pItemIDList = SHBrowseForFolder(&bi)) { wchar_t path[MAX_PATH]; path[0] = 0; if (SHGetPathFromIDList(pItemIDList, path) && path[0]) diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp index 48e0bba41f..c6f55c3509 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.cpp +++ b/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -28,6 +28,8 @@ #include <QtCore/qdebug.h> #include <QtCore/qbuffer.h> #include <QtCore/qpoint.h> +#include <QtCore/qpointer.h> +#include <QtCore/private/qcomobject_p.h> #include <shlobj.h> @@ -167,7 +169,7 @@ static Qt::MouseButtons lastButtons = Qt::NoButton; \internal */ -class QWindowsOleDropSource : public QWindowsComBase<IDropSource> +class QWindowsOleDropSource : public QComObject<IDropSource> { public: enum Mode { @@ -348,7 +350,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) } else { if (buttons && !m_currentButtons) { m_currentButtons = buttons; - } else if (!(m_currentButtons & buttons)) { // Button changed: Complete Drop operation. + } else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation. result = DRAGDROP_S_DROP; } } @@ -526,7 +528,8 @@ QWindowsOleDropTarget::DragLeave() qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window; - lastModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); + lastModifiers = keyMapper->queryKeyboardModifiers(); lastButtons = QWindowsMouseHandler::queryMouseButtons(); QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction, @@ -611,7 +614,6 @@ QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState, */ bool QWindowsDrag::m_canceled = false; -bool QWindowsDrag::m_dragging = false; QWindowsDrag::QWindowsDrag() = default; @@ -649,7 +651,8 @@ IDropTargetHelper* QWindowsDrag::dropHelper() { 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(); + const bool hasMouseCapture = underMouse && static_cast<QWindowsWindow *>(underMouse->handle())->hasMouseCapture(); + const HWND hwnd = hasMouseCapture ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus(); bool starting = false; for (;;) { @@ -738,10 +741,7 @@ Qt::DropAction QWindowsDrag::drag(QDrag *drag) 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) { diff --git a/src/plugins/platforms/windows/qwindowsdrag.h b/src/plugins/platforms/windows/qwindowsdrag.h index cf5d43fb84..a2d0e54044 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.h +++ b/src/plugins/platforms/windows/qwindowsdrag.h @@ -4,12 +4,12 @@ #ifndef QWINDOWSDRAG_H #define QWINDOWSDRAG_H -#include "qwindowscombase.h" #include "qwindowsinternalmimedata.h" #include <qpa/qplatformdrag.h> #include <QtGui/qpixmap.h> #include <QtGui/qdrag.h> +#include <QtCore/private/qcomobject_p.h> struct IDropTargetHelper; @@ -23,7 +23,7 @@ public: IDataObject *retrieveDataObject() const override; }; -class QWindowsOleDropTarget : public QWindowsComBase<IDropTarget> +class QWindowsOleDropTarget : public QComObject<IDropTarget> { public: explicit QWindowsOleDropTarget(QWindow *w); @@ -58,7 +58,6 @@ public: static QWindowsDrag *instance(); void cancelDrag() override { QWindowsDrag::m_canceled = true; } static bool isCanceled() { return QWindowsDrag::m_canceled; } - static bool isDragging() { return QWindowsDrag::m_dragging; } IDataObject *dropDataObject() const { return m_dropDataObject; } void setDropDataObject(IDataObject *dataObject) { m_dropDataObject = dataObject; } @@ -69,7 +68,6 @@ public: private: static bool m_canceled; - static bool m_dragging; QWindowsDropMimeData m_dropData; IDataObject *m_dropDataObject = nullptr; diff --git a/src/plugins/platforms/windows/qwindowsdropdataobject.cpp b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp index fbb2b45e21..629291640f 100644 --- a/src/plugins/platforms/windows/qwindowsdropdataobject.cpp +++ b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp @@ -9,6 +9,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + /*! \class QWindowsDropDataObject \brief QWindowsOleDataObject subclass specialized for handling Drag&Drop. @@ -56,8 +58,8 @@ bool QWindowsDropDataObject::shouldIgnore(LPFORMATETC pformatetc) const QString formatName = QWindowsMimeRegistry::clipboardFormatName(pformatetc->cfFormat); if (pformatetc->cfFormat == CF_UNICODETEXT || pformatetc->cfFormat == CF_TEXT - || formatName == QStringLiteral("UniformResourceLocator") - || formatName == QStringLiteral("UniformResourceLocatorW")) { + || formatName == "UniformResourceLocator"_L1 + || formatName == "UniformResourceLocatorW"_L1) { const auto urls = dropData->urls(); return std::all_of(urls.cbegin(), urls.cend(), [] (const QUrl &u) { return u.isLocalFile(); }); } diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp index cfd73519fa..5ca52c2c19 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp @@ -1262,7 +1262,6 @@ bool QWindowsGLContext::makeCurrent(QPlatformSurface *surface) // Do we already have a DC entry for that window? auto *window = static_cast<QWindowsWindow *>(surface); - window->aboutToMakeCurrent(); const HWND hwnd = window->handle(); if (const QOpenGLContextData *contextData = findByHWND(m_windowContexts, hwnd)) { // Repeated calls to wglMakeCurrent when vsync is enabled in the driver will diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp new file mode 100644 index 0000000000..5e5ca22ec1 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp @@ -0,0 +1,394 @@ +// 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 + +#include "qwindowsiconengine.h" + +#include <QtCore/qoperatingsystemversion.h> +#include <QtGui/qguiapplication.h> +#include <QtGui/qpainter.h> +#include <QtGui/qpalette.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QString QWindowsIconEngine::glyphs() const +{ + if (!QFontInfo(m_iconFont).exactMatch()) + return {}; + + static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = { + {"address-book-new"_L1, u"\ue780"}, + {"application-exit"_L1, u"\ue8bb"}, + {"appointment-new"_L1, u"\ue878"}, + {"call-start"_L1, u"\uf715"}, + {"call-stop"_L1, u"\uf405"}, + {"contact-new"_L1, u"\ue8fa"}, + {"document-new"_L1, u"\ue8a5"}, + {"document-open"_L1, u"\ue8e5"}, + {"document-open-recent"_L1, u"\ue823"}, + {"document-page-setup"_L1, u"\ue7c3"}, + {"document-print"_L1, u"\ue749"}, + {"document-print-preview"_L1, u"\ue956"}, + {"document-properties"_L1, u"\ue90f"}, + {"document-revert"_L1, u"\ue7a7"}, // ? + {"document-save"_L1, u"\ue74e"}, // or e78c? + {"document-save-as"_L1, u"\ue792"}, + {"document-send"_L1, u"\ue724"}, + {"edit-clear"_L1, u"\ue894"}, + {"edit-copy"_L1, u"\ue8c8"}, + {"edit-cut"_L1, u"\ue8c6"}, + {"edit-delete"_L1, u"\ue74d"}, + {"edit-find"_L1, u"\ue721"}, + //{"edit-find-replace"_L1, u"\u"}, + {"edit-paste"_L1, u"\ue77f"}, + {"edit-redo"_L1, u"\ue7a6"}, + {"edit-select-all"_L1, u"\ue8b3"}, + {"edit-undo"_L1, u"\ue7a7"}, + {"folder-new"_L1, u"\ue8f4"}, + //{"format-indent-less"_L1, u"\u"}, + //{"format-indent-more"_L1, u"\u"}, + {"format-justify-center"_L1, u"\ue8e3"}, + //{"format-justify-fill"_L1, u"\ue235"}, + {"format-justify-left"_L1, u"\ue8e4"}, + {"format-justify-right"_L1, u"\ue8e2"}, + //{"format-text-direction-ltr"_L1, u"\ue247"}, + //{"format-text-direction-rtl"_L1, u"\ue248"}, + {"format-text-bold"_L1, u"\ue8dd"}, + {"format-text-italic"_L1, u"\ue8db"}, + {"format-text-underline"_L1, u"\ue8dc"}, + {"format-text-strikethrough"_L1, u"\uede0"}, + //{"go-bottom"_L1,u"\ue258"}, + {"go-down"_L1,u"\ue74b"}, + //{"go-first"_L1, u"\ue5dc"}, + {"go-home"_L1, u"\ue80f"}, + // {"go-jump"_L1, u"\uf719"}, + //{"go-last"_L1, u"\ue5dd"}, + {"go-next"_L1, u"\ue893"}, + {"go-previous"_L1, u"\ue892"}, + //{"go-top"_L1, u"\ue25a"}, + {"go-up"_L1, u"\ue74a"}, + {"help-about"_L1, u"\ue946"}, + //{"help-contents"_L1, u"\ue8de"}, + {"help-faq"_L1, u"\ue897"}, + {"insert-image"_L1, u"\ue946"}, + {"insert-link"_L1, u"\ue71b"}, + //{"insert-object"_L1, u"\u"}, + //{"insert-text"_L1, u"\uf827"}, + {"list-add"_L1, u"\ue710"}, + {"list-remove"_L1, u"\ue738"}, + {"mail-forward"_L1, u"\ue89c"}, + //{"mail-mark-important"_L1, u"\ue937"}, + //{"mail-mark-junk"_L1, u"\u"}, + //{"mail-mark-notjunk"_L1, u"\u"}, + {"mail-mark-read"_L1, u"\ue8c3"}, + //{"mail-mark-unread"_L1, u"\ue9bc"}, + {"mail-message-new"_L1, u"\ue70f"}, + {"mail-reply-all"_L1, u"\ue8c2"}, + {"mail-reply-sender"_L1, u"\ue8ca"}, + {"mail-send"_L1, u"\ue724"}, + //{"mail-send-receive"_L1, u"\u"}, + {"media-eject"_L1, u"\uf847"}, + {"media-playback-pause"_L1, u"\ue769"}, + {"media-playback-start"_L1, u"\ue768"}, + {"media-playback-stop"_L1, u"\ue71a"}, + {"media-record"_L1, u"\ue7c8"}, + {"media-seek-backward"_L1, u"\ueb9e"}, + {"media-seek-forward"_L1, u"\ueb9d"}, + {"media-skip-backward"_L1, u"\ue892"}, + {"media-skip-forward"_L1, u"\ue893"}, + //{"object-flip-horizontal"_L1, u"\u"}, + //{"object-flip-vertical"_L1, u"\u"}, + {"object-rotate-left"_L1, u"\ue80c"}, + {"object-rotate-right"_L1, u"\ue80d"}, + //{"process-stop"_L1, u"\ue5c9"}, + {"system-lock-screen"_L1, u"\uee3f"}, + {"system-log-out"_L1, u"\uf3b1"}, + //{"system-run"_L1, u"\u"}, + {"system-search"_L1, u"\ue721"}, + {"system-reboot"_L1, u"\ue777"}, // unsure? + {"system-shutdown"_L1, u"\ue7e8"}, + {"tools-check-spelling"_L1, u"\uf87b"}, + {"view-fullscreen"_L1, u"\ue740"}, + {"view-refresh"_L1, u"\ue72c"}, + {"view-restore"_L1, u"\ue777"}, + //{"view-sort-ascending"_L1, u"\ue25a"}, + //{"view-sort-descending"_L1, u"\ue258"}, + {"window-close"_L1, u"\ue8bb"}, + {"window-new"_L1, u"\ue78b"}, + {"zoom-fit-best"_L1, u"\ue9a6"}, + {"zoom-in"_L1, u"\ue8a3"}, + //{"zoom-original"_L1, u"\u"}, + {"zoom-out"_L1, u"\ue71f"}, + + {"process-working"_L1, u"\ue9f3"}, + + {"accessories-calculator"_L1, u"\ue8ef"}, + {"accessories-character-map"_L1, u"\uf2b7"}, + {"accessories-dictionary"_L1, u"\ue82d"}, + //{"accessories-text-editor"_L1, u"\ue262"}, + {"help-browser"_L1, u"\ue897"}, + {"multimedia-volume-control"_L1, u"\ue767"}, + {"preferences-desktop-accessibility"_L1, u"\ue776"}, + {"preferences-desktop-font"_L1, u"\ue8d2"}, + {"preferences-desktop-keyboard"_L1, u"\ue765"}, + {"preferences-desktop-locale"_L1, u"\uf2b7"}, + //{"preferences-desktop-multimedia"_L1, u"\uea75"}, + {"preferences-desktop-screensaver"_L1, u"\uf182"}, + //{"preferences-desktop-theme"_L1, u"\uf560"}, + //{"preferences-desktop-wallpaper"_L1, u"\ue1bc"}, + {"system-file-manager"_L1, u"\uec50"}, + //{"system-software-install"_L1, u"\ueb71"}, + {"system-software-update"_L1, u"\uecc5"}, + {"utilities-system-monitor"_L1, u"\ue7f4"}, + //{"utilities-terminal"_L1, u"\ueb8e"}, + + //{"applications-accessories"_L1, u"\u"}, + {"applications-development"_L1, u"\uec7a"}, + //{"applications-engineering"_L1, u"\uea3d"}, + {"applications-games"_L1, u"\ue7fc"}, + //{"applications-graphics"_L1, u"\u"}, + {"applications-internet"_L1, u"\ue774"}, + {"applications-multimedia"_L1, u"\uea69"}, + //{"applications-office"_L1, u"\u"}, + //{"applications-other"_L1, u"\u"}, + //{"applications-science"_L1, u"\uea4b"}, + {"applications-system"_L1, u"\ue770"}, + //{"applications-utilities"_L1, u"\u"}, + //{"preferences-desktop"_L1, u"\ueb97"}, + //{"preferences-desktop-peripherals"_L1, u"\u"}, + //{"preferences-desktop-personal"_L1, u"\uf835"}, + //{"preferences-other"_L1, u"\u"}, + //{"preferences-system"_L1, u"\ue8b8"}, + //{"preferences-system-network"_L1, u"\ue894"}, + {"system-help"_L1, u"\ue946"}, + + {"audio-card"_L1, u"\ue8d6"}, + {"audio-input-microphone"_L1, u"\ue720"}, + {"battery"_L1, u"\ue83f"}, + {"camera-photo"_L1, u"\ue722"}, + {"camera-video"_L1, u"\ue714"}, + {"camera-web"_L1, u"\ue8b8"}, + {"computer"_L1, u"\ue7f8"}, // or e7fb? + {"drive-harddisk"_L1, u"\ueda2"}, + {"drive-optical"_L1, u"\ue958"}, + //{"drive-removable-media"_L1, u"\u"}, + //{"input-gaming"_L1, u"\u"}, + {"input-keyboard"_L1, u"\ue92e"}, + {"input-mouse"_L1, u"\ue962"}, + {"input-tablet"_L1, u"\ue70a"}, + {"media-flash"_L1, u"\ue88e"}, + //{"media-floppy"_L1, u"\u"}, + {"media-optical"_L1, u"\ue958"}, + {"media-tape"_L1, u"\ue96a"}, + //{"modem"_L1, u"\u"}, + //{"multimedia-player"_L1, u"\u"}, + {"network-wired"_L1, u"\ue968"}, + {"network-wireless"_L1, u"\ue701"}, + //{"pda"_L1, u"\u"}, + {"phone"_L1, u"\ue717"}, + {"printer"_L1, u"\ue749"}, + {"scanner"_L1, u"\ue8fe"}, + //{"video-display"_L1, u"\uf06a"}, + + {"emblem-default"_L1, u"\uf56d"}, + {"emblem-documents"_L1, u"\ue8a5"}, + {"emblem-downloads"_L1, u"\ue896"}, + {"emblem-favorite"_L1, u"\ue734"}, + {"emblem-important"_L1, u"\ue8c9"}, + {"emblem-mail"_L1, u"\ue715"}, + {"emblem-photos"_L1, u"\ue91b"}, + //{"emblem-readonly"_L1, u"\u"}, + {"emblem-shared"_L1, u"\ue902"}, + {"emblem-symbolic-link"_L1, u"\ue71b"}, + {"emblem-synchronized"_L1, u"\uedab"}, + {"emblem-system"_L1, u"\ue770"}, + //{"emblem-unreadable"_L1, u"\u"}, + + {"folder"_L1, u"\ue8b7"}, + //{"folder-remote"_L1, u"\u"}, + //{"network-server"_L1, u"\ue875"}, + //{"network-workgroup"_L1, u"\ue1a0"}, + {"start-here"_L1, u"\ue8fc"}, // unsure + {"user-bookmarks"_L1, u"\ue8a4"}, + //{"user-desktop"_L1, u"\ue30a"}, + {"user-home"_L1, u"\ue80f"}, + {"user-trash"_L1, u"\ue74d"}, + + //{"appointment-missed"_L1, u"\ue615"}, + //{"appointment-soon"_L1, u"\uf540"}, + {"audio-volume-high"_L1, u"\ue995"}, + {"audio-volume-low"_L1, u"\ue993"}, + {"audio-volume-medium"_L1, u"\ue994"}, + {"audio-volume-muted"_L1, u"\ue992"}, + //{"battery-caution"_L1, u"\ue19c"}, + {"battery-low"_L1, u"\ue851"}, // ? + {"dialog-error"_L1, u"\ue783"}, + {"dialog-information"_L1, u"\ue946"}, + //{"dialog-password"_L1, u"\uf042"}, + {"dialog-question"_L1, u"\uf142"}, // unsure + {"dialog-warning"_L1, u"\ue7ba"}, + //{"folder-drag-accept"_L1, @u"\ue9a3"}, + {"folder-open"_L1, u"\ue838"}, + //{"folder-visiting"_L1, u"\ue8a7"}, + //{"image-loading"_L1, u"\ue41a"}, + //{"image-missing"_L1, u"\ue3ad"}, + {"mail-attachment"_L1, u"\ue723"}, + //{"mail-unread"_L1, u"\uf18a"}, + //{"mail-read"_L1, u"\uf18c"}, + {"mail-replied"_L1, u"\ue8ca"}, + //{"mail-signed"_L1, u"\u"}, + //{"mail-signed-verified"_L1, u"\u"}, + {"media-playlist-repeat"_L1, u"\ue8ee"}, + {"media-playlist-shuffle"_L1, u"\ue8b1"}, + //{"network-error"_L1, u"\uead9"}, + //{"network-idle"_L1, u"\ue51f"}, + {"network-offline"_L1, u"\uf384"}, + //{"network-receive"_L1, u"\ue2c0"}, + //{"network-transmit"_L1, u"\ue2c3"}, + //{"network-transmit-receive"_L1, u"\uca18"}, + //{"printer-error"_L1, u"\uf7a0"}, + //{"printer-printing"_L1, u"\uf7a1"}, + //{"security-high"_L1, u"\ue32a"}, + //{"security-medium"_L1, u"\ue9e0"}, + //{"security-low"_L1, u"\uf012"}, + //{"software-update-available"_L1, u"\ue923"}, + //{"software-update-urgent"_L1, u"\uf05a"}, + {"sync-error"_L1, u"\uea6a"}, + {"sync-synchronizing"_L1, u"\ue895"}, + //{"task-due"_L1, u"\u"}, + //{"task-past-due"_L1, u"\u"}, + {"user-available"_L1, u"\ue8cf"}, + //{"user-away"_L1, u"\ue510"}, + //{"user-idle"_L1, u"\u"}, + //{"user-offline"_L1, u"\uf7b3"}, + //{"user-trash-full"_L1, u"\ue872"}, //delete + //{"user-trash-full"_L1, u"\ue92b"}, //delete_forever + {"weather-clear"_L1, u"\ue706"}, + //{"weather-clear-night"_L1, u"\uf159"}, + //{"weather-few-clouds"_L1, u"\uf172"}, + //{"weather-few-clouds-night"_L1, u"\uf174"}, + //{"weather-fog"_L1, u"\ue818"}, + {"weather-overcast"_L1, u"\ue753"}, + //{"weather-severe-alert"_L1, u"\ue002"}, //warning + //{"weather-showers"_L1, u"\uf176"}, + //{"weather-showers-scattered"_L1, u"\u"}, + //{"weather-snow"_L1, u"\ue80f"}, //snowing + //{"weather-storm"_L1, u"\uf070"}, + }; + + const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){ + return c.first == m_iconName; + }); + + return it != std::end(glyphMap) ? it->second.toString() + : (m_iconName.length() == 1 ? m_iconName : QString()); +} + +namespace { +auto iconFontFamily() +{ + static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; + return isWindows11 ? u"Segoe Fluent Icons"_s + : u"Segoe MDL2 Assets"_s; +} +} + +QWindowsIconEngine::QWindowsIconEngine(const QString &iconName) + : m_iconName(iconName), m_iconFont(iconFontFamily()) + , m_glyphs(glyphs()) +{ +} + +QWindowsIconEngine::~QWindowsIconEngine() +{} + +QIconEngine *QWindowsIconEngine::clone() const +{ + return new QWindowsIconEngine(m_iconName); +} + +QString QWindowsIconEngine::key() const +{ + return u"QWindowsIconEngine"_s; +} + +QString QWindowsIconEngine::iconName() +{ + return m_iconName; +} + +bool QWindowsIconEngine::isNull() +{ + if (m_glyphs.isEmpty()) + return true; + + const QChar c0 = m_glyphs.at(0); + const QFontMetrics fontMetrics(m_iconFont); + if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1) + return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1))); + return !fontMetrics.inFont(c0); +} + +QList<QSize> QWindowsIconEngine::availableSizes(QIcon::Mode, QIcon::State) +{ + return {{16, 16}, {24, 24}, {48, 48}, {128, 128}}; +} + +QSize QWindowsIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + return QIconEngine::actualSize(size, mode, state); +} + +QPixmap QWindowsIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + return scaledPixmap(size, mode, state, 1.0); +} + +QPixmap QWindowsIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) +{ + const quint64 cacheKey = calculateCacheKey(mode, state); + if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) { + m_pixmap = QPixmap(size * scale); + m_pixmap.fill(Qt::transparent); + m_pixmap.setDevicePixelRatio(scale); + + QPainter painter(&m_pixmap); + paint(&painter, QRect(QPoint(), size), mode, state); + + m_cacheKey = cacheKey; + } + + return m_pixmap; +} + +void QWindowsIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + painter->save(); + QFont renderFont(m_iconFont); + renderFont.setPixelSize(rect.height()); + painter->setFont(renderFont); + + QPalette palette; + switch (mode) { + case QIcon::Active: + painter->setPen(palette.color(QPalette::Active, QPalette::Text)); + break; + case QIcon::Normal: + painter->setPen(palette.color(QPalette::Active, QPalette::Text)); + break; + case QIcon::Disabled: + painter->setPen(palette.color(QPalette::Disabled, QPalette::Text)); + break; + case QIcon::Selected: + painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText)); + break; + } + + painter->drawText(rect, Qt::AlignCenter, m_glyphs); + painter->restore(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsiconengine.h b/src/plugins/platforms/windows/qwindowsiconengine.h new file mode 100644 index 0000000000..3c6cbddb8b --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsiconengine.h @@ -0,0 +1,47 @@ +// 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 QWINDOWSICONENGINE_H +#define QWINDOWSICONENGINE_H + +#include <QtCore/qt_windows.h> + +#include <QtGui/qfont.h> +#include <QtGui/qiconengine.h> + +QT_BEGIN_NAMESPACE + +class QWindowsIconEngine : public QIconEngine +{ +public: + QWindowsIconEngine(const QString &iconName); + ~QWindowsIconEngine(); + QIconEngine *clone() const override; + QString key() const override; + QString iconName() override; + bool isNull() override; + + QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override; + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override; + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + +private: + static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state) + { + return (quint64(mode) << 32) | state; + } + + QString glyphs() const; + + const QString m_iconName; + const QFont m_iconFont; + const QString m_glyphs; + mutable QPixmap m_pixmap; + mutable quint64 m_cacheKey = {}; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSICONENGINE_H diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.cpp b/src/plugins/platforms/windows/qwindowsinputcontext.cpp index 758490ffb7..0281025b5b 100644 --- a/src/plugins/platforms/windows/qwindowsinputcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsinputcontext.cpp @@ -676,7 +676,7 @@ int QWindowsInputContext::reconvertString(RECONVERTSTRING *reconv) reconv->dwTargetStrOffset = reconv->dwCompStrOffset; auto *pastReconv = reinterpret_cast<ushort *>(reconv + 1); std::copy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(), - QT_MAKE_UNCHECKED_ARRAY_ITERATOR(pastReconv)); + pastReconv); return memSize; } diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index 628b2f3209..aa6be266da 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -80,31 +80,6 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -/*! - \class QWindowsIntegration - \brief QPlatformIntegration implementation for Windows. - \internal - - \section1 Programming Considerations - - The platform plugin should run on Desktop Windows from Windows XP onwards - and Windows Embedded. - - It should compile with: - \list - \li Microsoft Visual Studio 2013 or later (using the Microsoft Windows SDK, - (\c Q_CC_MSVC). - \li Stock \l{http://mingw.org/}{MinGW} (\c Q_CC_MINGW). - This version ships with headers that are missing a lot of WinAPI. - \li MinGW distributions using GCC 4.7 or higher and a recent MinGW-w64 runtime API, - such as \l{http://tdm-gcc.tdragon.net/}{TDM-GCC}, or - \l{http://mingwbuilds.sourceforge.net/}{MinGW-builds} - (\c Q_CC_MINGW and \c __MINGW64_VERSION_MAJOR indicating the version). - MinGW-w64 provides more complete headers (compared to stock MinGW from mingw.org), - including a considerable part of the Windows SDK. - \endlist -*/ - struct QWindowsIntegrationPrivate { Q_DISABLE_COPY_MOVE(QWindowsIntegrationPrivate) @@ -167,8 +142,8 @@ static inline unsigned parseOptions(const QStringList ¶mList, unsigned options = 0; for (const QString ¶m : paramList) { if (param.startsWith(u"fontengine=")) { - if (param.endsWith(u"directwrite")) { - options |= QWindowsIntegration::FontDatabaseDirectWrite; + if (param.endsWith(u"gdi")) { + options |= QWindowsIntegration::FontDatabaseGDI; } else if (param.endsWith(u"freetype")) { options |= QWindowsIntegration::FontDatabaseFreeType; } else if (param.endsWith(u"native")) { @@ -281,9 +256,9 @@ QWindowsIntegration::~QWindowsIntegration() void QWindowsIntegration::initialize() { - QString icStr = QPlatformInputContextFactory::requested(); - icStr.isNull() ? d->m_inputContext.reset(new QWindowsInputContext) - : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStr)); + auto icStrs = QPlatformInputContextFactory::requested(); + icStrs.isEmpty() ? d->m_inputContext.reset(new QWindowsInputContext) + : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStrs)); } bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) const @@ -311,6 +286,8 @@ bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) co return true; case SwitchableWidgetComposition: return false; // QTBUG-68329 QTBUG-53515 QTBUG-54734 + case BackingStoreStaticContents: + return true; default: return QPlatformIntegration::hasCapability(cap); } @@ -507,17 +484,17 @@ QWindowsStaticOpenGLContext *QWindowsIntegration::staticOpenGLContext() QPlatformFontDatabase *QWindowsIntegration::fontDatabase() const { if (!d->m_fontDatabase) { -#if QT_CONFIG(directwrite3) - if (d->m_options & QWindowsIntegration::FontDatabaseDirectWrite) - d->m_fontDatabase = new QWindowsDirectWriteFontDatabase; - else -#endif #ifndef QT_NO_FREETYPE if (d->m_options & QWindowsIntegration::FontDatabaseFreeType) d->m_fontDatabase = new QWindowsFontDatabaseFT; else #endif // QT_NO_FREETYPE - d->m_fontDatabase = new QWindowsFontDatabase(); +#if QT_CONFIG(directwrite3) + if (!(d->m_options & (QWindowsIntegration::FontDatabaseGDI | QWindowsIntegration::DontUseDirectWriteFonts))) + d->m_fontDatabase = new QWindowsDirectWriteFontDatabase; + else +#endif + d->m_fontDatabase = new QWindowsFontDatabase; } return d->m_fontDatabase; } @@ -565,14 +542,9 @@ QVariant QWindowsIntegration::styleHint(QPlatformIntegration::StyleHint hint) co return QPlatformIntegration::styleHint(hint); } -Qt::KeyboardModifiers QWindowsIntegration::queryKeyboardModifiers() const +QPlatformKeyMapper *QWindowsIntegration::keyMapper() const { - return QWindowsKeyMapper::queryKeyboardModifiers(); -} - -QList<int> QWindowsIntegration::possibleKeys(const QKeyEvent *e) const -{ - return d->m_context.possibleKeys(e); + return d->m_context.keyMapper(); } #if QT_CONFIG(clipboard) @@ -653,12 +625,16 @@ void QWindowsIntegration::setApplicationBadge(qint64 number) // We prefer the native BadgeUpdater API, that allows us to set a number directly, // but it requires that the application has a package identity, and also doesn't // seem to work in all cases on < Windows 11. - if (isWindows11 && qt_win_hasPackageIdentity()) { - using namespace winrt::Windows::UI::Notifications; - auto badgeXml = BadgeUpdateManager::GetTemplateContent(BadgeTemplateType::BadgeNumber); - badgeXml.SelectSingleNode(L"//badge/@value").NodeValue(winrt::box_value(winrt::to_hstring(number))); - BadgeUpdateManager::CreateBadgeUpdaterForApplication().Update(BadgeNotification(badgeXml)); - return; + QT_TRY { + if (isWindows11 && qt_win_hasPackageIdentity()) { + using namespace winrt::Windows::UI::Notifications; + auto badgeXml = BadgeUpdateManager::GetTemplateContent(BadgeTemplateType::BadgeNumber); + badgeXml.SelectSingleNode(L"//badge/@value").NodeValue(winrt::box_value(winrt::to_hstring(number))); + BadgeUpdateManager::CreateBadgeUpdaterForApplication().Update(BadgeNotification(badgeXml)); + return; + } + } QT_CATCH(...) { + // fall back to win32 implementation } #endif @@ -670,7 +646,8 @@ void QWindowsIntegration::setApplicationBadge(qint64 number) return; } - const bool isDarkMode = QWindowsContext::isDarkMode(); + const bool isDarkMode = QWindowsTheme::instance()->colorScheme() + == Qt::ColorScheme::Dark; QColor badgeColor; QColor textColor; @@ -785,7 +762,8 @@ void QWindowsIntegration::updateApplicationBadge() // to a task bar button being created for the fist time or after // Explorer had crashed and re-started. In any case, re-apply the // badge so that everything is up to date. - setApplicationBadge(m_applicationBadgeNumber); + if (m_applicationBadgeNumber) + setApplicationBadge(m_applicationBadgeNumber); } #if QT_CONFIG(vulkan) diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h index 1f3c71c41b..c271207741 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.h +++ b/src/plugins/platforms/windows/qwindowsintegration.h @@ -45,7 +45,7 @@ public: DontUseWMPointer = 0x400, DetectAltGrModifier = 0x800, RtlEnabled = 0x1000, - FontDatabaseDirectWrite = 0x2000 + FontDatabaseGDI = 0x2000 }; explicit QWindowsIntegration(const QStringList ¶mList); @@ -82,8 +82,7 @@ public: QPlatformServices *services() const override; QVariant styleHint(StyleHint hint) const override; - Qt::KeyboardModifiers queryKeyboardModifiers() const override; - QList<int> possibleKeys(const QKeyEvent *e) const override; + QPlatformKeyMapper *keyMapper() const override; static QWindowsIntegration *instance() { return m_instance; } diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp index 3089e745d4..ba76cda40b 100644 --- a/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -88,9 +88,17 @@ QWindowsKeyMapper::~QWindowsKeyMapper()= default; #define VK_OEM_3 0xC0 #endif -// We not only need the scancode itself but also the extended bit of key messages. Thus we need -// the additional bit when masking the scancode. -enum { scancodeBitmask = 0x1ff }; +// Get scancode from the given message +static constexpr quint32 getScancode(const MSG &msg) +{ + const auto keyFlags = HIWORD(msg.lParam); + quint32 scancode = LOBYTE(keyFlags); + // if extended-key flag is on, the scan code consists of a sequence of two bytes, + // where the first byte has a value of 0xe0. + if ((keyFlags & KF_EXTENDED) != 0) + scancode |= 0xE000; + return scancode; +} // Key recorder ------------------------------------------------------------------------[ start ] -- struct KeyRecord { @@ -532,33 +540,6 @@ QDebug operator<<(QDebug d, const KeyboardLayoutItem &k) d << ')'; return d; } - -// Helpers to format a list of int as Qt key sequence -class formatKeys -{ -public: - explicit formatKeys(const QList<int> &keys) : m_keys(keys) {} - -private: - friend QDebug operator<<(QDebug d, const formatKeys &keys); - const QList<int> &m_keys; -}; - -QDebug operator<<(QDebug d, const formatKeys &k) -{ - QDebugStateSaver saver(d); - d.nospace(); - d << '('; - for (int i =0, size = k.m_keys.size(); i < size; ++i) { - if (i) - d << ", "; - d << QKeySequence(k.m_keys.at(i)); - } - d << ')'; - return d; -} -#else // !QT_NO_DEBUG_STREAM -static int formatKeys(const QList<int> &) { return 0; } #endif // QT_NO_DEBUG_STREAM /** @@ -656,7 +637,7 @@ void QWindowsKeyMapper::updateKeyMap(const MSG &msg) { unsigned char kbdBuffer[256]; // Will hold the complete keyboard state GetKeyboardState(kbdBuffer); - const quint32 scancode = (msg.lParam >> 16) & scancodeBitmask; + const quint32 scancode = getScancode(msg); updatePossibleKeyCodes(kbdBuffer, scancode, quint32(msg.wParam)); } @@ -820,7 +801,7 @@ static void showSystemMenu(QWindow* w) qWindowsWndProc(topLevelHwnd, WM_SYSCOMMAND, WPARAM(ret), 0); } -static inline void sendExtendedPressRelease(QWindow *w, int k, +static inline void sendExtendedPressRelease(QWindow *w, unsigned long timestamp, int k, Qt::KeyboardModifiers mods, quint32 nativeScanCode, quint32 nativeVirtualKey, @@ -829,8 +810,8 @@ static inline void sendExtendedPressRelease(QWindow *w, int k, bool autorep = false, ushort count = 1) { - QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyPress, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count); - QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyRelease, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count); + QWindowSystemInterface::handleExtendedKeyEvent(w, timestamp, QEvent::KeyPress, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count); + QWindowSystemInterface::handleExtendedKeyEvent(w, timestamp, QEvent::KeyRelease, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count); } /*! @@ -897,7 +878,7 @@ bool QWindowsKeyMapper::translateMultimediaKeyEventInternal(QWindow *window, con const int qtKey = int(CmdTbl[cmd]); if (!skipPressRelease) - sendExtendedPressRelease(receiver, qtKey, Qt::KeyboardModifier(state), 0, 0, 0); + sendExtendedPressRelease(receiver, msg.time, qtKey, Qt::KeyboardModifier(state), 0, 0, 0); // QTBUG-43343: Make sure to return false if Qt does not handle the key, otherwise, // the keys are not passed to the active media player. # if QT_CONFIG(shortcut) @@ -942,7 +923,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, m_seenAltGr = true; const UINT msgType = msg.message; - const quint32 scancode = (msg.lParam >> 16) & scancodeBitmask; + const quint32 scancode = getScancode(msg); auto vk_key = quint32(msg.wParam); quint32 nModifiers = 0; @@ -977,7 +958,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, // A multi-character key or a Input method character // not found by our look-ahead if (msgType == WM_CHAR || msgType == WM_IME_CHAR) { - sendExtendedPressRelease(receiver, 0, Qt::KeyboardModifier(state), scancode, vk_key, nModifiers, messageKeyText(msg), false); + sendExtendedPressRelease(receiver, msg.time, 0, Qt::KeyboardModifier(state), scancode, 0, nModifiers, messageKeyText(msg), false); return true; } @@ -1012,14 +993,14 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, if (dirStatus == VK_LSHIFT && ((msg.wParam == VK_SHIFT && GetKeyState(VK_LCONTROL)) || (msg.wParam == VK_CONTROL && GetKeyState(VK_LSHIFT)))) { - sendExtendedPressRelease(receiver, Qt::Key_Direction_L, {}, + sendExtendedPressRelease(receiver, msg.time, Qt::Key_Direction_L, {}, scancode, vk_key, nModifiers, QString(), false); result = true; dirStatus = 0; } else if (dirStatus == VK_RSHIFT && ( (msg.wParam == VK_SHIFT && GetKeyState(VK_RCONTROL)) || (msg.wParam == VK_CONTROL && GetKeyState(VK_RSHIFT)))) { - sendExtendedPressRelease(receiver, Qt::Key_Direction_R, {}, + sendExtendedPressRelease(receiver, msg.time, Qt::Key_Direction_R, {}, scancode, vk_key, nModifiers, QString(), false); result = true; dirStatus = 0; @@ -1232,9 +1213,9 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, // so, we have an auto-repeating key if (rec) { if (code < Qt::Key_Shift || code > Qt::Key_ScrollLock) { - QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code, + QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code, Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true); - QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code, + QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code, Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true); result = true; } @@ -1260,7 +1241,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, if (msg.wParam == VK_PACKET) code = asciiToKeycode(char(uch.cell()), state); - QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code, + QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code, modifiers, scancode, quint32(msg.wParam), nModifiers, text, false); result =true; bool store = true; @@ -1302,10 +1283,10 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, if ((msg.lParam & 0x40000000) == 0 && Qt::KeyboardModifier(state) == Qt::NoModifier && ((code == Qt::Key_F18) || (code == Qt::Key_F19) || (code == Qt::Key_F20))) { - QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code, + QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code, Qt::MetaModifier, scancode, quint32(msg.wParam), MetaLeft); - QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code, + QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code, Qt::NoModifier, scancode, quint32(msg.wParam), 0); result = true; @@ -1317,7 +1298,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, // Map SHIFT + Tab to SHIFT + BackTab, QShortcutMap knows about this translation if (code == Qt::Key_Tab && (state & Qt::ShiftModifier) == Qt::ShiftModifier) code = Qt::Key_Backtab; - QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code, + QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code, Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, (rec ? rec->text : QString()), false); @@ -1339,7 +1320,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg, return result; } -Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers() +Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers() const { Qt::KeyboardModifiers modifiers = Qt::NoModifier; if (GetKeyState(VK_SHIFT) < 0) @@ -1353,9 +1334,9 @@ Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers() return modifiers; } -QList<int> QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const +QList<QKeyCombination> QWindowsKeyMapper::possibleKeyCombinations(const QKeyEvent *e) const { - QList<int> result; + QList<QKeyCombination> result; const quint32 nativeVirtualKey = e->nativeVirtualKey(); @@ -1369,31 +1350,34 @@ QList<int> QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const quint32 baseKey = kbItem.qtKey[0]; Qt::KeyboardModifiers keyMods = e->modifiers(); if (baseKey == Qt::Key_Return && (e->nativeModifiers() & ExtendedKey)) { - result << (Qt::Key_Enter | keyMods).toCombined(); + result << (Qt::Key_Enter | keyMods); return result; } - result << int(baseKey) + int(keyMods); // The base key is _always_ valid, of course + + // The base key is _always_ valid, of course + result << QKeyCombination::fromCombined(int(baseKey) + int(keyMods)); for (size_t i = 1; i < NumMods; ++i) { Qt::KeyboardModifiers neededMods = ModsTbl[i]; quint32 key = kbItem.qtKey[i]; if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) { const Qt::KeyboardModifiers missingMods = keyMods & ~neededMods; - const int matchedKey = int(key) + int(missingMods); - const auto it = - std::find_if(result.begin(), result.end(), - [key] (int k) { return (k & ~Qt::KeyboardModifierMask) == key; }); + const auto matchedKey = QKeyCombination::fromCombined(int(key) + int(missingMods)); + const auto it = std::find_if(result.begin(), result.end(), + [key](auto keyCombination) { + return keyCombination.key() == key; + }); // QTBUG-67200: Use the match with the least modifiers (prefer // Shift+9 over Alt + Shift + 9) resulting in more missing modifiers. if (it == result.end()) result << matchedKey; - else if (missingMods > Qt::KeyboardModifiers(*it & Qt::KeyboardModifierMask)) + else if (missingMods > it->keyboardModifiers()) *it = matchedKey; } } qCDebug(lcQpaEvents) << __FUNCTION__ << e << "nativeVirtualKey=" << Qt::showbase << Qt::hex << e->nativeVirtualKey() << Qt::dec << Qt::noshowbase - << e->modifiers() << kbItem << "\n returns" << formatKeys(result); + << e->modifiers() << kbItem << "\n returns" << result; return result; } diff --git a/src/plugins/platforms/windows/qwindowskeymapper.h b/src/plugins/platforms/windows/qwindowskeymapper.h index 6e13c47323..72b2536ad7 100644 --- a/src/plugins/platforms/windows/qwindowskeymapper.h +++ b/src/plugins/platforms/windows/qwindowskeymapper.h @@ -8,6 +8,8 @@ #include <QtCore/qlocale.h> +#include <qpa/qplatformkeymapper.h> + QT_BEGIN_NAMESPACE class QKeyEvent; @@ -33,7 +35,7 @@ struct KeyboardLayoutItem { quint32 qtKey[NumQtKeys]; // Can by any Qt::Key_<foo>, or unicode character }; -class QWindowsKeyMapper +class QWindowsKeyMapper : public QPlatformKeyMapper { Q_DISABLE_COPY_MOVE(QWindowsKeyMapper) public: @@ -53,8 +55,8 @@ public: QWindow *keyGrabber() const { return m_keyGrabber; } void setKeyGrabber(QWindow *w) { m_keyGrabber = w; } - static Qt::KeyboardModifiers queryKeyboardModifiers(); - QList<int> possibleKeys(const QKeyEvent *e) const; + Qt::KeyboardModifiers queryKeyboardModifiers() const override; + QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *e) const override; private: bool translateKeyEventInternal(QWindow *receiver, MSG msg, bool grab, LRESULT *lResult); diff --git a/src/plugins/platforms/windows/qwindowsmenu.h b/src/plugins/platforms/windows/qwindowsmenu.h index 646ab64894..6f66180d82 100644 --- a/src/plugins/platforms/windows/qwindowsmenu.h +++ b/src/plugins/platforms/windows/qwindowsmenu.h @@ -9,7 +9,6 @@ #include <qpa/qplatformmenu.h> #include <QtCore/qlist.h> -#include <QtCore/qpair.h> QT_BEGIN_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp index 2d1c42df91..8d147e8fa0 100644 --- a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp +++ b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp @@ -154,7 +154,7 @@ static bool qt_write_dibv5(QDataStream &s, QImage image) return false; if (image.format() != QImage::Format_ARGB32) - image = image.convertToFormat(QImage::Format_ARGB32); + image = std::move(image).convertToFormat(QImage::Format_ARGB32); auto *buf = new uchar[bpl_bmp]; @@ -870,7 +870,7 @@ bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeD QByteArray ba; if (cf == CF_DIB) { if (img.format() > QImage::Format_ARGB32) - img = img.convertToFormat(QImage::Format_RGB32); + img = std::move(img).convertToFormat(QImage::Format_RGB32); const QByteArray ba = writeDib(img); if (!ba.isEmpty()) return setData(ba, pmedium); @@ -883,7 +883,7 @@ bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeD } else { QDataStream s(&ba, QIODevice::WriteOnly); s.setByteOrder(QDataStream::LittleEndian);// Intel byte order #### - if (qt_write_dibv5(s, img)) + if (qt_write_dibv5(s, std::move(img))) return setData(ba, pmedium); } } diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp index ceb33f6141..9af9fba408 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp @@ -268,7 +268,8 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, } } - const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); + const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers(); const MouseEvent mouseEvent = eventFromMsg(msg); Qt::MouseButtons buttons; @@ -286,19 +287,16 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove) && (m_lastEventButton & buttons) == 0) { - if (mouseEvent.type == QEvent::NonClientAreaMouseMove) { - QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, clientPosition, globalPosition, buttons, m_lastEventButton, - QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source); - } else { - QWindowSystemInterface::handleMouseEvent(window, device, clientPosition, globalPosition, buttons, m_lastEventButton, - QEvent::MouseButtonRelease, keyModifiers, source); - } + auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ? + QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease; + QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, m_lastEventButton, + releaseType, keyModifiers, source); } m_lastEventType = mouseEvent.type; m_lastEventButton = mouseEvent.button; if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) { - QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, clientPosition, + QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, mouseEvent.button, mouseEvent.type, keyModifiers, source); @@ -448,7 +446,7 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, } if (!discardEvent && mouseEvent.type != QEvent::None) { - QWindowSystemInterface::handleMouseEvent(window, device, clientPosition, globalPosition, buttons, + QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, mouseEvent.button, mouseEvent.type, keyModifiers, source); } @@ -472,7 +470,7 @@ static bool isValidWheelReceiver(QWindow *candidate) return false; } -static void redirectWheelEvent(QWindow *window, const QPoint &globalPos, int delta, +static void redirectWheelEvent(QWindow *window, unsigned long timestamp, const QPoint &globalPos, int delta, Qt::Orientation orientation, Qt::KeyboardModifiers mods) { // Redirect wheel event to one of the following, in order of preference: @@ -493,6 +491,7 @@ static void redirectWheelEvent(QWindow *window, const QPoint &globalPos, int del if (handleEvent) { const QPoint point = (orientation == Qt::Vertical) ? QPoint(0, delta) : QPoint(delta, 0); QWindowSystemInterface::handleWheelEvent(receiver, + timestamp, QWindowsGeometryHint::mapFromGlobal(receiver, globalPos), globalPos, QPoint(), point, mods); } @@ -521,7 +520,7 @@ bool QWindowsMouseHandler::translateMouseWheelEvent(QWindow *window, HWND, delta = -delta; const QPoint globalPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); - redirectWheelEvent(window, globalPos, delta, orientation, mods); + redirectWheelEvent(window, msg.time, globalPos, delta, orientation, mods); return true; } @@ -552,7 +551,7 @@ bool QWindowsMouseHandler::translateScrollEvent(QWindow *window, HWND, return false; } - redirectWheelEvent(window, QCursor::pos(), delta, Qt::Horizontal, Qt::NoModifier); + redirectWheelEvent(window, msg.time, QCursor::pos(), delta, Qt::Horizontal, Qt::NoModifier); return true; } @@ -632,10 +631,12 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, if (allStates == QEventPoint::State::Released) m_touchInputIDToTouchPointID.clear(); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); QWindowSystemInterface::handleTouchEvent(window, + msg.time, m_touchDevice.data(), touchPoints, - QWindowsKeyMapper::queryKeyboardModifiers()); + keyMapper->queryKeyboardModifiers()); return true; } diff --git a/src/plugins/platforms/windows/qwindowsole.h b/src/plugins/platforms/windows/qwindowsole.h index b687384684..016f9dd04c 100644 --- a/src/plugins/platforms/windows/qwindowsole.h +++ b/src/plugins/platforms/windows/qwindowsole.h @@ -4,12 +4,12 @@ #ifndef QWINDOWSOLE_H #define QWINDOWSOLE_H -#include "qwindowscombase.h" #include <QtCore/qt_windows.h> #include <QtCore/qlist.h> #include <QtCore/qmap.h> #include <QtCore/qpointer.h> +#include <QtCore/private/qcomobject_p.h> #include <objidl.h> @@ -18,7 +18,7 @@ QT_BEGIN_NAMESPACE class QMimeData; class QWindow; -class QWindowsOleDataObject : public QWindowsComBase<IDataObject> +class QWindowsOleDataObject : public QComObject<IDataObject> { public: explicit QWindowsOleDataObject(QMimeData *mimeData); @@ -47,7 +47,7 @@ private: DWORD performedEffect = DROPEFFECT_NONE; }; -class QWindowsOleEnumFmtEtc : public QWindowsComBase<IEnumFORMATETC> +class QWindowsOleEnumFmtEtc : public QComObject<IEnumFORMATETC> { public: explicit QWindowsOleEnumFmtEtc(const QList<FORMATETC> &fmtetcs); diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp index 88f02347b3..71c7217671 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -428,8 +428,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, return false; if (msg.message == WM_POINTERCAPTURECHANGED) { - QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice.data(), - QWindowsKeyMapper::queryKeyboardModifiers()); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); + QWindowSystemInterface::handleTouchCancelEvent(window, msg.time, m_touchDevice.data(), + keyMapper->queryKeyboardModifiers()); m_lastTouchPoints.clear(); return true; } @@ -441,6 +442,8 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, 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. @@ -537,8 +540,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, if (allStates == QEventPoint::State::Released) m_touchInputIDToTouchPointID.clear(); - QWindowSystemInterface::handleTouchEvent(window, m_touchDevice.data(), touchPoints, - QWindowsKeyMapper::queryKeyboardModifiers()); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); + QWindowSystemInterface::handleTouchEvent(window, msg.time, m_touchDevice.data(), touchPoints, + keyMapper->queryKeyboardModifiers()); return false; // Allow mouse messages to be generated. } @@ -635,7 +639,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin switch (msg.message) { case WM_POINTERENTER: { - QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), true); + QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), true); m_windowUnderPointer = window; // The local coordinates may fall outside the window. // Wait until the next update to send the enter event. @@ -648,7 +652,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin m_windowUnderPointer = nullptr; m_currentWindow = nullptr; } - QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), false); + QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), false); break; case WM_POINTERDOWN: case WM_POINTERUP: @@ -671,9 +675,10 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin wumPlatformWindow->applyCursor(); } } - const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); + const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers(); - QWindowSystemInterface::handleTabletEvent(target, device.data(), + QWindowSystemInterface::handleTabletEvent(target, msg.time, device.data(), localPos, hiResGlobalPos, mouseButtons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, keyModifiers); @@ -724,7 +729,7 @@ bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window, QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos); - QWindowSystemInterface::handleWheelEvent(receiver, localPos, globalPos, QPoint(), angleDelta, keyModifiers); + QWindowSystemInterface::handleWheelEvent(receiver, msg.time, localPos, globalPos, QPoint(), angleDelta, keyModifiers); return true; } @@ -737,20 +742,20 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, { *result = 0; - QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); - if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd)) { - RECT clientArea; - GetClientRect(hwnd, &clientArea); - eventPos.setX(clientArea.right - eventPos.x()); - } - 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()) @@ -760,7 +765,8 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, : QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos); } - const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); + const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers(); QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos); if (et == QtWindows::MouseWheelEvent) @@ -820,19 +826,16 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove) && (m_lastEventButton & mouseButtons) == 0) { - if (mouseEvent.type == QEvent::NonClientAreaMouseMove) { - QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, localPos, globalPos, mouseButtons, m_lastEventButton, - QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source); - } else { - QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons, m_lastEventButton, - QEvent::MouseButtonRelease, keyModifiers, source); - } + auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ? + QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease; + QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton, + releaseType, keyModifiers, source); } m_lastEventType = mouseEvent.type; m_lastEventButton = mouseEvent.button; if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) { - QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, localPos, globalPos, mouseButtons, + QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, mouseEvent.button, mouseEvent.type, keyModifiers, source); return false; // Allow further event processing } @@ -852,7 +855,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, handleEnterLeave(window, currentWindowUnderPointer, globalPos); if (!discardEvent && mouseEvent.type != QEvent::None) { - QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons, + QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, mouseEvent.button, mouseEvent.type, keyModifiers, source); } diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index 5414fc2778..a50f9fd4b0 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -14,14 +14,22 @@ #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 <private/quniquehandle_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 @@ -42,7 +50,7 @@ static inline QDpi monitorDPI(HMONITOR hMonitor) return {0, 0}; } -static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *pathInfo) +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; @@ -58,7 +66,7 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO * // look up the needed buffer sizes. if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements, &numModeInfoArrayElements) != ERROR_SUCCESS) { - return false; + return {}; } pathInfos.resize(numPathArrayElements); modeInfos.resize(numModeInfoArrayElements); @@ -67,24 +75,25 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO * } while (result == ERROR_INSUFFICIENT_BUFFER); if (result != ERROR_SUCCESS) - return false; - - // Find path matching monitor name - for (uint32_t p = 0; p < numPathArrayElements; p++) { - 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 = pathInfos[p].sourceInfo.adapterId; - deviceName.header.id = pathInfos[p].sourceInfo.id; - if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) { - if (wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) == 0) { - *pathInfo = pathInfos[p]; + 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 false; + return pathInfos; } #if 0 @@ -108,6 +117,149 @@ static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo) using WindowsScreenDataList = QList<QWindowsScreenData>; +namespace { + +struct DiRegKeyHandleTraits +{ + using Type = HKEY; + static Type invalidValue() + { + // The setupapi.h functions return INVALID_HANDLE_VALUE when failing to open a registry key + return reinterpret_cast<HKEY>(INVALID_HANDLE_VALUE); + } + static bool close(Type handle) { return RegCloseKey(handle) == ERROR_SUCCESS; } +}; + +using DiRegKeyHandle = QUniqueHandle<DiRegKeyHandleTraits>; + +} + +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 DiRegKeyHandle edidRegistryKey{ SetupDiOpenDevRegKey( + devInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) }; + + if (!edidRegistryKey.isValid()) + 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; @@ -120,16 +272,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) 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); - DISPLAYCONFIG_PATH_INFO pathInfo = {}; - const bool hasPathInfo = getPathInfo(info, &pathInfo); - if (hasPathInfo) { - 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 = pathInfo.targetInfo.adapterId; - deviceName.header.id = pathInfo.targetInfo.id; - if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) - data->name = QString::fromWCharArray(deviceName.monitorFriendlyDeviceName); + const auto pathGroup = getPathInfo(info); + if (!pathGroup.empty()) { + setMonitorDataFromSetupApi(*data, pathGroup); } if (data->name.isEmpty()) data->name = data->deviceName; @@ -155,7 +300,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) // ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO, // if we are going to use DISPLAYCONFIG lookups more. - if (hasPathInfo) { + 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; @@ -231,15 +378,14 @@ 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; + 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) @@ -390,10 +536,14 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) 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(), @@ -406,6 +556,8 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) QWindowSystemInterface::handleScreenGeometryChange(screen(), newData.geometry, newData.availableGeometry); } + if (primaryChanged) + QWindowSystemInterface::handlePrimaryScreenChanged(this); } HMONITOR QWindowsScreen::handle() const @@ -517,7 +669,8 @@ extern "C" LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT if (QWindowsTheme *t = QWindowsTheme::instance()) t->displayChanged(); QWindowsWindow::displayChanged(); - QWindowsContext::instance()->screenManager().handleScreenChanges(); + if (auto *context = QWindowsContext::instance()) + context->screenManager().handleScreenChanges(); } return DefWindowProc(hwnd, message, wParam, lParam); diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h index b1c94d2204..0467ab2a0c 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.h +++ b/src/plugins/platforms/windows/qwindowsscreen.h @@ -7,10 +7,9 @@ #include "qtwindowsglobal.h" #include <QtCore/qlist.h> -#include <QtCore/qpair.h> #include <QtCore/qscopedpointer.h> #include <qpa/qplatformscreen.h> -#include <qpa/qplatformscreen_p.h> +#include <QtGui/qscreen_platform.h> QT_BEGIN_NAMESPACE @@ -31,15 +30,18 @@ struct QWindowsScreenData QImage::Format format = QImage::Format_ARGB32_Premultiplied; unsigned flags = VirtualDesktop; QString name; + QString manufacturer; + QString model; + QString serialNumber; Qt::ScreenOrientation orientation = Qt::LandscapeOrientation; qreal refreshRateHz = 60; HMONITOR hMonitor = nullptr; - QString deviceName = {}; + QString deviceName; + QString devicePath; std::optional<int> deviceIndex = std::nullopt; }; -class QWindowsScreen : public QPlatformScreen - , public QNativeInterface::Private::QWindowsScreen +class QWindowsScreen : public QPlatformScreen, public QNativeInterface::QWindowsScreen { public: #ifndef QT_NO_CURSOR @@ -58,6 +60,9 @@ public: qreal devicePixelRatio() const override { return 1.0; } qreal refreshRate() const override { return m_data.refreshRateHz; } QString name() const override; + QString manufacturer() const override { return m_data.manufacturer; } + QString model() const override { return m_data.model; } + QString serialNumber() const override { return m_data.serialNumber; } Qt::ScreenOrientation orientation() const override { return m_data.orientation; } QList<QPlatformScreen *> virtualSiblings() const override; QWindow *topLevelAt(const QPoint &point) const override; diff --git a/src/plugins/platforms/windows/qwindowsservices.cpp b/src/plugins/platforms/windows/qwindowsservices.cpp index d9bb76d10a..89f93fd161 100644 --- a/src/plugins/platforms/windows/qwindowsservices.cpp +++ b/src/plugins/platforms/windows/qwindowsservices.cpp @@ -154,10 +154,15 @@ static inline bool launchMail(const QUrl &url) qWarning("Cannot launch '%ls': There is no mail program installed.", qUtf16Printable(url.toString())); return false; } + // Fix mail launch if no param is expected in this command. + if (command.indexOf("%1"_L1) < 0) { + qWarning() << "The mail command lacks the '%1' parameter."; + return false; + } //Make sure the path for the process is in quotes const QChar doubleQuote = u'"'; if (!command.startsWith(doubleQuote)) { - const int exeIndex = command.indexOf(QStringLiteral(".exe "), 0, Qt::CaseInsensitive); + const int exeIndex = command.indexOf(".exe "_L1, 0, Qt::CaseInsensitive); if (exeIndex != -1) { command.insert(exeIndex + 4, doubleQuote); command.prepend(doubleQuote); diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp index 3bad237f9e..6f0680ac23 100644 --- a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp +++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp @@ -24,6 +24,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + static const UINT q_uNOTIFYICONID = 0; static uint MYWM_TASKBARCREATED = 0; @@ -116,7 +118,7 @@ static inline HWND createTrayIconMessageWindow() return nullptr; // Register window class in the platform plugin. const QString className = - ctx->registerWindowClass(QWindowsContext::classNamePrefix() + QStringLiteral("TrayIconMessageWindowClass"), + ctx->registerWindowClass(QWindowsContext::classNamePrefix() + "TrayIconMessageWindowClass"_L1, qWindowsTrayIconWndProc); const wchar_t windowName[] = L"QTrayIconMessageWindow"; return CreateWindowEx(0, reinterpret_cast<const wchar_t *>(className.utf16()), @@ -162,8 +164,6 @@ void QWindowsSystemTrayIcon::cleanup() void QWindowsSystemTrayIcon::updateIcon(const QIcon &icon) { qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << icon << ')' << this; - if (icon.cacheKey() == m_icon.cacheKey()) - return; m_icon = icon; const HICON hIconToDestroy = createIcon(icon); if (ensureInstalled()) @@ -184,6 +184,9 @@ void QWindowsSystemTrayIcon::updateToolTip(const QString &tooltip) QRect QWindowsSystemTrayIcon::geometry() const { + if (!isIconVisible()) + return QRect(); + NOTIFYICONIDENTIFIER nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); @@ -217,25 +220,19 @@ void QWindowsSystemTrayIcon::showMessage(const QString &title, const QString &me qStringToLimitedWCharArray(title, tnd.szInfoTitle, 64); tnd.uID = q_uNOTIFYICONID; - tnd.dwInfoFlags = NIIF_USER; - - QSize size(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); - const QSize largeIcon(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); - const QSize more = icon.actualSize(largeIcon); - if (more.height() > (largeIcon.height() * 3/4) || more.width() > (largeIcon.width() * 3/4)) { - tnd.dwInfoFlags |= NIIF_LARGE_ICON; - size = largeIcon; - } + + const auto size = icon.actualSize(QSize(256, 256)); QPixmap pm = icon.pixmap(size); + if (m_hMessageIcon) { + DestroyIcon(m_hMessageIcon); + m_hMessageIcon = nullptr; + } if (pm.isNull()) { tnd.dwInfoFlags = NIIF_INFO; } else { - if (pm.size() != size) { - qWarning("QSystemTrayIcon::showMessage: Wrong icon size (%dx%d), please add standard one: %dx%d", - pm.size().width(), pm.size().height(), size.width(), size.height()); - pm = pm.scaled(size, Qt::IgnoreAspectRatio); - } - tnd.hBalloonIcon = qt_pixmapToWinHICON(pm); + tnd.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON; + m_hMessageIcon = qt_pixmapToWinHICON(pm); + tnd.hBalloonIcon = m_hMessageIcon; } tnd.hWnd = m_hwnd; tnd.uTimeout = msecsIn <= 0 ? UINT(10000) : UINT(msecsIn); // 10s default @@ -293,7 +290,10 @@ void QWindowsSystemTrayIcon::ensureCleanup() } if (m_hIcon != nullptr) DestroyIcon(m_hIcon); + if (m_hMessageIcon != nullptr) + DestroyIcon(m_hMessageIcon); m_hIcon = nullptr; + m_hMessageIcon = nullptr; m_menu = nullptr; // externally owned m_toolTip.clear(); } @@ -310,6 +310,29 @@ bool QWindowsSystemTrayIcon::setIconVisible(bool visible) return Shell_NotifyIcon(NIM_MODIFY, &tnd) == TRUE; } +bool QWindowsSystemTrayIcon::isIconVisible() const +{ + NOTIFYICONIDENTIFIER nid; + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(nid); + nid.hWnd = m_hwnd; + nid.uID = q_uNOTIFYICONID; + RECT rect; + const HRESULT hr = Shell_NotifyIconGetRect(&nid, &rect); + // Windows 10 returns S_FALSE if the icon is hidden + if (FAILED(hr) || hr == S_FALSE) + return false; + + HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO info; + info.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(monitor, &info); + // Windows 11 seems to return a geometry outside of the current monitor's geometry in case of + // the icon being hidden. As it's impossible to change the alignment of the task bar on Windows + // 11 this check should be fine. + return rect.bottom <= info.rcMonitor.bottom; +} + bool QWindowsSystemTrayIcon::sendTrayMessage(DWORD msg) { NOTIFYICONDATA tnd; diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.h b/src/plugins/platforms/windows/qwindowssystemtrayicon.h index 2545ae9101..3ad5feb125 100644 --- a/src/plugins/platforms/windows/qwindowssystemtrayicon.h +++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.h @@ -49,12 +49,14 @@ private: void ensureCleanup(); bool sendTrayMessage(DWORD msg); bool setIconVisible(bool visible); + bool isIconVisible() const; HICON createIcon(const QIcon &icon); QIcon m_icon; QString m_toolTip; HWND m_hwnd = nullptr; HICON m_hIcon = nullptr; + HICON m_hMessageIcon = nullptr; mutable QPointer<QWindowsPopupMenu> m_menu; bool m_ignoreNextMouseRelease = false; bool m_visible = false; diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp index b7cc2bf77b..ceebb483d2 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -595,7 +595,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() << "mode=" << m_mode; } - const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); + const auto *keyMapper = QWindowsContext::instance()->keyMapper(); + const Qt::KeyboardModifiers keyboardModifiers = keyMapper->queryKeyboardModifiers(); for (int i = 0; i < packetCount ; ++i) { const PACKET &packet = localPacketBuf[i]; diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index 6fba9e55c1..b6017c7692 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -7,26 +7,29 @@ #include "qwindowsmenu.h" #include "qwindowsdialoghelpers.h" #include "qwindowscontext.h" +#include "qwindowsiconengine.h" #include "qwindowsintegration.h" #if QT_CONFIG(systemtrayicon) # include "qwindowssystemtrayicon.h" #endif #include "qwindowsscreen.h" +#include "qwindowswindow.h" #include <commctrl.h> #include <objbase.h> -#ifndef Q_CC_MINGW -# include <commoncontrols.h> -#endif +#include <commoncontrols.h> #include <shellapi.h> +#include <QtCore/qapplicationstatic.h> #include <QtCore/qvariant.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qdebug.h> #include <QtCore/qsysinfo.h> #include <QtCore/qcache.h> #include <QtCore/qthread.h> +#include <QtCore/qqueue.h> #include <QtCore/qmutex.h> #include <QtCore/qwaitcondition.h> +#include <QtCore/qoperatingsystemversion.h> #include <QtGui/qcolor.h> #include <QtGui/qpalette.h> #include <QtGui/qguiapplication.h> @@ -48,10 +51,6 @@ # include <winrt/Windows.UI.ViewManagement.h> #endif // QT_CONFIG(cpp_winrt) -#if defined(__IImageList_INTERFACE_DEFINED__) && defined(__IID_DEFINED__) -# define USE_IIMAGELIST -#endif - QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -79,11 +78,11 @@ static inline QColor mixColors(const QColor &c1, const QColor &c2) (c1.blue() + c2.blue()) / 2}; } -static inline QColor getSysColor(int index) -{ - COLORREF cr = GetSysColor(index); - return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr)); -} +enum AccentColorLevel { + AccentColorDarkest, + AccentColorNormal, + AccentColorLightest +}; #if QT_CONFIG(cpp_winrt) static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color) @@ -92,111 +91,146 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color) } #endif -// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system -// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the -// behavior by running it in a thread. +[[maybe_unused]] [[nodiscard]] static inline QColor qt_accentColor(AccentColorLevel level) +{ +#if QT_CONFIG(cpp_winrt) + using namespace winrt::Windows::UI::ViewManagement; + const auto settings = UISettings(); + const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); + const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); + const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); +#else + const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); + if (!registry.isValid()) + return {}; + const QVariant value = registry.value(L"AccentColor"); + if (!value.isValid()) + return {}; + // The retrieved value is in the #AABBGGRR format, we need to + // convert it to the #AARRGGBB format which Qt expects. + const QColor abgr = QColor::fromRgba(qvariant_cast<DWORD>(value)); + if (!abgr.isValid()) + return {}; + const QColor accent = QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); + const QColor accentLight = accent.lighter(120); + const QColor accentDarkest = accent.darker(120 * 120 * 120); +#endif + if (level == AccentColorDarkest) + return accentDarkest; + else if (level == AccentColorLightest) + return accentLight; + return accent; +} -struct QShGetFileInfoParams +static inline QColor getSysColor(int index) { - QShGetFileInfoParams(const QString &fn, DWORD a, SHFILEINFO *i, UINT f, bool *r) - : fileName(fn), attributes(a), flags(f), info(i), result(r) - { } - - const QString &fileName; - const DWORD attributes; - const UINT flags; - SHFILEINFO *const info; - bool *const result; -}; + COLORREF cr = GetSysColor(index); + return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr)); +} +// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system +// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the +// behavior by running it in a thread. class QShGetFileInfoThread : public QThread { public: - explicit QShGetFileInfoThread() - : QThread(), m_params(nullptr) + struct Task + { + Task(const QString &fn, DWORD a, UINT f) + : fileName(fn), attributes(a), flags(f) + {} + Q_DISABLE_COPY(Task) + ~Task() + { + DestroyIcon(hIcon); + hIcon = 0; + } + // Request + const QString fileName; + const DWORD attributes; + const UINT flags; + // Result + HICON hIcon = 0; + int iIcon = -1; + bool finished = false; + bool resultValid() const { return hIcon != 0 && iIcon >= 0 && finished; } + }; + + QShGetFileInfoThread() + : QThread() + { + start(); + } + + ~QShGetFileInfoThread() { - connect(this, &QThread::finished, this, &QObject::deleteLater); + cancel(); + wait(); + } + + QSharedPointer<Task> getNextTask() + { + QMutexLocker l(&m_waitForTaskMutex); + while (!isInterruptionRequested()) { + if (!m_taskQueue.isEmpty()) + return m_taskQueue.dequeue(); + m_waitForTaskCondition.wait(&m_waitForTaskMutex); + } + return nullptr; } void run() override { QComHelper comHelper(COINIT_MULTITHREADED); - QMutexLocker readyLocker(&m_readyMutex); - while (!m_cancelled.loadRelaxed()) { - if (!m_params && !m_cancelled.loadRelaxed() - && !m_readyCondition.wait(&m_readyMutex, QDeadlineTimer(1000ll))) - continue; - - if (m_params) { - const QString fileName = m_params->fileName; + while (!isInterruptionRequested()) { + auto task = getNextTask(); + if (task) { SHFILEINFO info; - const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(fileName.utf16()), - m_params->attributes, &info, sizeof(SHFILEINFO), - m_params->flags); - m_doneMutex.lock(); - if (!m_cancelled.loadRelaxed()) { - *m_params->result = result; - memcpy(m_params->info, &info, sizeof(SHFILEINFO)); + const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(task->fileName.utf16()), + task->attributes, &info, sizeof(SHFILEINFO), + task->flags); + if (result) { + task->hIcon = info.hIcon; + task->iIcon = info.iIcon; } - m_params = nullptr; - + task->finished = true; m_doneCondition.wakeAll(); - m_doneMutex.unlock(); } } } - bool runWithParams(QShGetFileInfoParams *params, qint64 timeOutMSecs) + void runWithParams(const QSharedPointer<Task> &task, + std::chrono::milliseconds timeout = std::chrono::milliseconds(5000)) { - QMutexLocker doneLocker(&m_doneMutex); - - m_readyMutex.lock(); - m_params = params; - m_readyCondition.wakeAll(); - m_readyMutex.unlock(); + { + QMutexLocker l(&m_waitForTaskMutex); + m_taskQueue.enqueue(task); + m_waitForTaskCondition.wakeAll(); + } - return m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeOutMSecs)); + QMutexLocker doneLocker(&m_doneMutex); + while (!task->finished && !isInterruptionRequested()) { + if (!m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeout))) + return; + } } void cancel() { - QMutexLocker doneLocker(&m_doneMutex); - m_cancelled.storeRelaxed(1); - m_readyCondition.wakeAll(); + requestInterruption(); + m_doneCondition.wakeAll(); + m_waitForTaskCondition.wakeAll(); } private: - QShGetFileInfoParams *m_params; - QAtomicInt m_cancelled; - QWaitCondition m_readyCondition; + QQueue<QSharedPointer<Task>> m_taskQueue; QWaitCondition m_doneCondition; - QMutex m_readyMutex; + QWaitCondition m_waitForTaskCondition; QMutex m_doneMutex; + QMutex m_waitForTaskMutex; }; - -static bool shGetFileInfoBackground(const QString &fileName, DWORD attributes, - SHFILEINFO *info, UINT flags, - qint64 timeOutMSecs = 5000) -{ - static QShGetFileInfoThread *getFileInfoThread = nullptr; - if (!getFileInfoThread) { - getFileInfoThread = new QShGetFileInfoThread; - getFileInfoThread->start(); - } - - bool result = false; - QShGetFileInfoParams params(fileName, attributes, info, flags, &result); - if (!getFileInfoThread->runWithParams(¶ms, timeOutMSecs)) { - // Cancel and reset getFileInfoThread. It'll - // be reinitialized the next time we get called. - getFileInfoThread->cancel(); - getFileInfoThread = nullptr; - qWarning().noquote() << "SHGetFileInfo() timed out for " << fileName; - return false; - } - return result; -} +Q_APPLICATION_STATIC(QShGetFileInfoThread, s_shGetFileInfoThread) // from QStyle::standardPalette static inline QPalette standardPalette() @@ -227,22 +261,17 @@ static QColor placeHolderColor(QColor textColor) */ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) { - QColor background = getSysColor(COLOR_BTNFACE); - QColor textColor = getSysColor(COLOR_WINDOWTEXT); - QColor accent = getSysColor(COLOR_HIGHLIGHT); - -#if QT_CONFIG(cpp_winrt) - // respect the Windows 11 accent color - using namespace winrt::Windows::UI::ViewManagement; - const auto settings = UISettings(); + const QColor background = getSysColor(COLOR_BTNFACE); + const QColor textColor = getSysColor(COLOR_WINDOWTEXT); - accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); -#endif + const QColor accent = qt_accentColor(AccentColorNormal); + const QColor accentDarkest = qt_accentColor(AccentColorDarkest); + const QColor linkColor = accent; const QColor btnFace = background; const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT); - result.setColor(QPalette::Highlight, accent); + result.setColor(QPalette::Highlight, getSysColor(COLOR_HIGHLIGHT)); result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT)); result.setColor(QPalette::Button, btnFace); result.setColor(QPalette::Light, btnHighlight); @@ -257,9 +286,10 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT)); result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW)); result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT)); + result.setColor(QPalette::Accent, accent); - result.setColor(QPalette::Link, Qt::blue); - result.setColor(QPalette::LinkVisited, Qt::magenta); + result.setColor(QPalette::Link, linkColor); + result.setColor(QPalette::LinkVisited, accentDarkest); result.setColor(QPalette::Inactive, QPalette::Button, result.button().color()); result.setColor(QPalette::Inactive, QPalette::Window, result.window().color()); result.setColor(QPalette::Inactive, QPalette::Light, result.light().color()); @@ -269,8 +299,11 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) result.setColor(QPalette::Midlight, result.button().color().lighter(110)); } -static void populateDarkSystemBasePalette(QPalette &result) +void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result) { + QColor foreground, background, + accent, accentDark, accentDarker, accentDarkest, + accentLight, accentLighter, accentLightest; #if QT_CONFIG(cpp_winrt) using namespace winrt::Windows::UI::ViewManagement; const auto settings = UISettings(); @@ -278,34 +311,38 @@ static void populateDarkSystemBasePalette(QPalette &result) // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API // returns the old system colors, not the dark mode colors. If the background is black (which it // usually), then override it with a dark gray instead so that we can go up and down the lightness. - const QColor foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); - const QColor background = [&settings]() -> QColor { - auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); - if (systemBackground == Qt::black) - systemBackground = QColor(0x1E, 0x1E, 0x1E); - return systemBackground; - }(); - - const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); - const QColor accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1)); - const QColor accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2)); - const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); - const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); - const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2)); - const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3)); - const QColor linkColor = accent; -#else - const QColor foreground = Qt::white; - const QColor background = QColor(0x1E, 0x1E, 0x1E); - const QColor accent = QColor(0x00, 0x55, 0xff); - const QColor accentDark = accent.darker(120); - const QColor accentDarker = accentDark.darker(120); - const QColor accentDarkest = accentDarker.darker(120); - const QColor accentLight = accent.lighter(120); - const QColor accentLighter = accentLight.lighter(120); - const QColor accentLightest = accentLighter.lighter(120); - const QColor linkColor = Qt::blue; + if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) { + // the system is actually running in dark mode, so UISettings will give us dark colors + foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); + background = [&settings]() -> QColor { + auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); + if (systemBackground == Qt::black) + systemBackground = QColor(0x1E, 0x1E, 0x1E); + return systemBackground; + }(); + + accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); + accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1)); + accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2)); + accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); + accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); + accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2)); + accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3)); + } else #endif + { + // If the system is running in light mode, then we need to make up our own dark palette + foreground = Qt::white; + background = QColor(0x1E, 0x1E, 0x1E); + accent = qt_accentColor(AccentColorNormal); + accentDark = accent.darker(120); + accentDarker = accentDark.darker(120); + accentDarkest = accentDarker.darker(120); + accentLight = accent.lighter(120); + accentLighter = accentLight.lighter(120); + accentLightest = accentLighter.lighter(120); + } + const QColor linkColor = accent; const QColor buttonColor = background.lighter(200); result.setColor(QPalette::All, QPalette::WindowText, foreground); @@ -331,36 +368,7 @@ static void populateDarkSystemBasePalette(QPalette &result) result.setColor(QPalette::All, QPalette::ToolTipBase, buttonColor); result.setColor(QPalette::All, QPalette::ToolTipText, foreground.darker(120)); result.setColor(QPalette::All, QPalette::PlaceholderText, placeHolderColor(foreground)); -} - -static QPalette systemPalette(bool light) -{ - QPalette result = standardPalette(); - if (light) - QWindowsTheme::populateLightSystemBasePalette(result); - else - populateDarkSystemBasePalette(result); - - if (result.window() != result.base()) { - result.setColor(QPalette::Inactive, QPalette::Highlight, - result.color(QPalette::Inactive, QPalette::Window)); - result.setColor(QPalette::Inactive, QPalette::HighlightedText, - result.color(QPalette::Inactive, QPalette::Text)); - } - - const QColor disabled = mixColors(result.windowText().color(), result.button().color()); - - result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(), - result.light(), result.dark(), result.mid(), - result.text(), result.brightText(), result.base(), - result.window()); - result.setColor(QPalette::Disabled, QPalette::WindowText, disabled); - result.setColor(QPalette::Disabled, QPalette::Text, disabled); - result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled); - result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight)); - result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText)); - result.setColor(QPalette::Disabled, QPalette::Base, result.window().color()); - return result; + result.setColor(QPalette::All, QPalette::Accent, accent); } static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light) @@ -452,6 +460,7 @@ QWindowsTheme *QWindowsTheme::m_instance = nullptr; QWindowsTheme::QWindowsTheme() { m_instance = this; + s_colorScheme = QWindowsTheme::queryColorScheme(); std::fill(m_fonts, m_fonts + NFonts, nullptr); std::fill(m_palettes, m_palettes + NPalettes, nullptr); refresh(); @@ -473,7 +482,10 @@ static inline QStringList iconThemeSearchPaths() static inline QStringList styleNames() { - return { QStringLiteral("WindowsVista"), QStringLiteral("Windows") }; + QStringList styles = { QStringLiteral("WindowsVista"), QStringLiteral("Windows") }; + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11) + styles.prepend(QStringLiteral("Windows11")); + return styles; } static inline int uiEffects() @@ -538,7 +550,43 @@ QVariant QWindowsTheme::themeHint(ThemeHint hint) const Qt::ColorScheme QWindowsTheme::colorScheme() const { - return QWindowsContext::isDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light; + return QWindowsTheme::effectiveColorScheme(); +} + +Qt::ColorScheme QWindowsTheme::effectiveColorScheme() +{ + if (queryHighContrast()) + return Qt::ColorScheme::Unknown; + if (s_colorSchemeOverride != Qt::ColorScheme::Unknown) + return s_colorSchemeOverride; + if (s_colorScheme != Qt::ColorScheme::Unknown) + return s_colorScheme; + return queryColorScheme(); +} + +void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + s_colorSchemeOverride = scheme; + handleSettingsChanged(); +} + +void QWindowsTheme::handleSettingsChanged() +{ + const auto oldColorScheme = s_colorScheme; + s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry + const auto newColorScheme = effectiveColorScheme(); + const bool colorSchemeChanged = newColorScheme != oldColorScheme; + s_colorScheme = newColorScheme; + auto integration = QWindowsIntegration::instance(); + integration->updateApplicationBadge(); + if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) { + QWindowsTheme::instance()->refresh(); + QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>(); + } + if (colorSchemeChanged) { + for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows())) + w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark); + } } void QWindowsTheme::clearPalettes() @@ -549,41 +597,66 @@ void QWindowsTheme::clearPalettes() void QWindowsTheme::refreshPalettes() { - if (!QGuiApplication::desktopSettingsAware()) return; const bool light = - !QWindowsContext::isDarkMode() + effectiveColorScheme() != Qt::ColorScheme::Dark || !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle); clearPalettes(); - m_palettes[SystemPalette] = new QPalette(systemPalette(light)); + m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(s_colorScheme)); m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light)); m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light)); m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light); if (!light) { -#if QT_CONFIG(cpp_winrt) - using namespace winrt::Windows::UI::ViewManagement; - const auto settings = UISettings(); - const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); - const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); - const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); - m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]); - m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, accent); - m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, accentLight); - m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, accentDarkest); -#else - m_palettes[ButtonPalette] = new QPalette(*m_palettes[SystemPalette]); - m_palettes[ButtonPalette]->setColor(QPalette::Button, QColor(0x666666u)); - const QColor checkBoxBlue(0x0078d7u); m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]); - m_palettes[CheckBoxPalette]->setColor(QPalette::Base, checkBoxBlue); - m_palettes[CheckBoxPalette]->setColor(QPalette::Button, checkBoxBlue); - m_palettes[CheckBoxPalette]->setColor(QPalette::ButtonText, Qt::white); -#endif + m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal)); + m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLightest)); + m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest)); m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]); } } +QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme) +{ + QPalette result = standardPalette(); + + switch (colorScheme) { + case Qt::ColorScheme::Unknown: + // when a high-contrast theme is active or when we fail to read, assume light + Q_FALLTHROUGH(); + case Qt::ColorScheme::Light: + populateLightSystemBasePalette(result); + break; + case Qt::ColorScheme::Dark: + populateDarkSystemBasePalette(result); + break; + } + + if (result.window() != result.base()) { + result.setColor(QPalette::Inactive, QPalette::Highlight, + result.color(QPalette::Inactive, QPalette::Window)); + result.setColor(QPalette::Inactive, QPalette::HighlightedText, + result.color(QPalette::Inactive, QPalette::Text)); + result.setColor(QPalette::Inactive, QPalette::Accent, + result.color(QPalette::Inactive, QPalette::Window)); + } + + const QColor disabled = mixColors(result.windowText().color(), result.button().color()); + + result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(), + result.light(), result.dark(), result.mid(), + result.text(), result.brightText(), result.base(), + result.window()); + result.setColor(QPalette::Disabled, QPalette::WindowText, disabled); + result.setColor(QPalette::Disabled, QPalette::Text, disabled); + result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled); + result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight)); + result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText)); + result.setColor(QPalette::Disabled, QPalette::Accent, disabled); + result.setColor(QPalette::Disabled, QPalette::Base, result.window().color()); + return result; +} + void QWindowsTheme::clearFonts() { qDeleteAll(m_fonts, m_fonts + NFonts); @@ -695,11 +768,7 @@ void QWindowsTheme::refreshIconPixmapSizes() fileIconSizes[LargeFileIcon] + fileIconSizes[LargeFileIcon] / 2; fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work -#ifdef USE_IIMAGELIST int *availEnd = fileIconSizes + JumboFileIcon + 1; -#else - int *availEnd = fileIconSizes + LargeFileIcon + 1; -#endif // USE_IIMAGELIST m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd); qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes; } @@ -812,15 +881,18 @@ QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSiz } if (stockId != SIID_INVALID) { - QPixmap pixmap; SHSTOCKICONINFO iconInfo; memset(&iconInfo, 0, sizeof(iconInfo)); iconInfo.cbSize = sizeof(iconInfo); - stockFlags |= (pixmapSize.width() > 16 ? SHGFI_LARGEICON : SHGFI_SMALLICON); - if (SHGetStockIconInfo(stockId, SHGFI_ICON | stockFlags, &iconInfo) == S_OK) { - pixmap = qt_pixmapFromWinHICON(iconInfo.hIcon); - DestroyIcon(iconInfo.hIcon); - return pixmap; + stockFlags |= SHGSI_ICONLOCATION; + if (SHGetStockIconInfo(stockId, stockFlags, &iconInfo) == S_OK) { + const auto iconSize = pixmapSize.width(); + HICON icon; + if (SHDefExtractIcon(iconInfo.szPath, iconInfo.iIcon, 0, &icon, nullptr, iconSize) == S_OK) { + QPixmap pixmap = qt_pixmapFromWinHICON(icon); + DestroyIcon(icon); + return pixmap; + } } } @@ -890,10 +962,9 @@ public: // Shell image list helper functions. -static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info) +static QPixmap pixmapFromShellImageList(int iImageList, int iIcon) { QPixmap result; -#ifdef USE_IIMAGELIST // For MinGW: static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}}; @@ -902,16 +973,12 @@ static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info) if (hr != S_OK) return result; HICON hIcon; - hr = imageList->GetIcon(info.iIcon, ILD_TRANSPARENT, &hIcon); + hr = imageList->GetIcon(iIcon, ILD_TRANSPARENT, &hIcon); if (hr == S_OK) { result = qt_pixmapFromWinHICON(hIcon); DestroyIcon(hIcon); } imageList->Release(); -#else - Q_UNUSED(iImageList); - Q_UNUSED(info); -#endif // USE_IIMAGELIST return result; } @@ -963,13 +1030,9 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon const int width = int(size.width()); const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON; const int requestedImageListSize = -#ifdef USE_IIMAGELIST width > fileIconSizes[ExtraLargeFileIcon] ? sHIL_JUMBO : (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0); -#else - 0; -#endif // !USE_IIMAGELIST bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot(); if (cacheableDirIcon) { QMutexLocker locker(&mx); @@ -985,7 +1048,6 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon } } - SHFILEINFO info; unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX; DWORD attributes = 0; QString path = filePath; @@ -997,43 +1059,43 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon flags |= SHGFI_USEFILEATTRIBUTES; attributes |= FILE_ATTRIBUTE_NORMAL; } - const bool val = shGetFileInfoBackground(path, attributes, &info, flags); - + auto task = QSharedPointer<QShGetFileInfoThread::Task>( + new QShGetFileInfoThread::Task(path, attributes, flags)); + s_shGetFileInfoThread()->runWithParams(task); // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases - if (val && info.hIcon) { + if (task->resultValid()) { QString key; if (cacheableDirIcon) { if (useDefaultFolderIcon && defaultFolderIIcon < 0) - defaultFolderIIcon = info.iIcon; + defaultFolderIIcon = task->iIcon; //using the unique icon index provided by windows save us from duplicate keys - key = dirIconPixmapCacheKey(info.iIcon, iconSize, requestedImageListSize); + key = dirIconPixmapCacheKey(task->iIcon, iconSize, requestedImageListSize); QPixmapCache::find(key, &pixmap); if (!pixmap.isNull()) { QMutexLocker locker(&mx); - dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon)); + dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon)); } } if (pixmap.isNull()) { if (requestedImageListSize) { - pixmap = pixmapFromShellImageList(requestedImageListSize, info); + pixmap = pixmapFromShellImageList(requestedImageListSize, task->iIcon); if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO) - pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, info); + pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, task->iIcon); } if (pixmap.isNull()) - pixmap = qt_pixmapFromWinHICON(info.hIcon); + pixmap = qt_pixmapFromWinHICON(task->hIcon); if (!pixmap.isNull()) { if (cacheableDirIcon) { QMutexLocker locker(&mx); QPixmapCache::insert(key, pixmap); - dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon)); + dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon)); } } else { qWarning("QWindowsTheme::fileIconPixmap() no icon found"); } } - DestroyIcon(info.hIcon); } return pixmap; @@ -1044,6 +1106,11 @@ QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOpt return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions)); } +QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const +{ + return new QWindowsIconEngine(iconName); +} + static inline bool doUseNativeMenus() { const unsigned options = QWindowsIntegration::instance()->options(); @@ -1068,19 +1135,23 @@ bool QWindowsTheme::useNativeMenus() return result; } -bool QWindowsTheme::queryDarkMode() +Qt::ColorScheme QWindowsTheme::queryColorScheme() { - if (queryHighContrast()) { - return false; - } + if (queryHighContrast()) + return Qt::ColorScheme::Unknown; + const auto setting = QWinRegistryKey(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)") .dwordValue(L"AppsUseLightTheme"); - return setting.second && setting.first == 0; + return setting.second && setting.first == 0 ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light; } bool QWindowsTheme::queryHighContrast() { - return booleanSystemParametersInfo(SPI_GETHIGHCONTRAST, false); + HIGHCONTRAST hcf = {}; + hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST)); + if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE)) + return hcf.dwFlags & HCF_HIGHCONTRASTON; + return false; } QPlatformMenuItem *QWindowsTheme::createPlatformMenuItem() const diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index 7f320da967..96ae6197b4 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -32,6 +32,10 @@ public: QVariant themeHint(ThemeHint) const override; Qt::ColorScheme colorScheme() const override; + void requestColorScheme(Qt::ColorScheme scheme) override; + Qt::ColorScheme requestedColorScheme() const { return s_colorSchemeOverride; } + + static void handleSettingsChanged(); const QPalette *palette(Palette type = SystemPalette) const override { return m_palettes[type]; } @@ -41,6 +45,7 @@ public: QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions = {}) const override; + QIconEngine *createIconEngine(const QString &iconName) const override; void windowsThemeChanged(QWindow *window); void displayChanged() { refreshIconPixmapSizes(); } @@ -53,15 +58,13 @@ public: void showPlatformMenuBar() override; static bool useNativeMenus(); - static bool queryDarkMode(); - static bool queryHighContrast(); void refreshFonts(); void refresh(); static const char *name; - static void populateLightSystemBasePalette(QPalette &result); + static QPalette systemPalette(Qt::ColorScheme); private: void clearPalettes(); @@ -69,7 +72,17 @@ private: void clearFonts(); void refreshIconPixmapSizes(); + static void populateLightSystemBasePalette(QPalette &result); + static void populateDarkSystemBasePalette(QPalette &result); + + static Qt::ColorScheme queryColorScheme(); + static Qt::ColorScheme effectiveColorScheme(); + static bool queryHighContrast(); + static QWindowsTheme *m_instance; + static inline Qt::ColorScheme s_colorScheme = Qt::ColorScheme::Unknown; + static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown; + QPalette *m_palettes[NPalettes]; QFont *m_fonts[NFonts]; QList<QSize> m_fileIconSizes; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index d0107145af..ee0b88ba54 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -5,6 +5,7 @@ #include "qwindowswindow.h" #include "qwindowscontext.h" +#include "qwindowstheme.h" #if QT_CONFIG(draganddrop) # include "qwindowsdrag.h" #endif @@ -27,11 +28,11 @@ #include <QtGui/qwindow.h> #include <QtGui/qregion.h> #include <QtGui/qopenglcontext.h> +#include <QtGui/private/qwindowsthemecache_p.h> #include <private/qwindow_p.h> // QWINDOWSIZE_MAX #include <private/qguiapplication_p.h> #include <private/qhighdpiscaling_p.h> #include <qpa/qwindowsysteminterface.h> -#include <qpa/qplatformtheme.h> #include <QtCore/qdebug.h> #include <QtCore/qlibraryinfo.h> @@ -430,11 +431,7 @@ static inline bool windowIsAccelerated(const QWindow *w) { switch (w->surfaceType()) { case QSurface::OpenGLSurface: - return true; - case QSurface::RasterGLSurface: - return qt_window_private(const_cast<QWindow *>(w))->compositing; case QSurface::VulkanSurface: - return true; case QSurface::Direct3DSurface: return true; default: @@ -469,14 +466,21 @@ static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags) w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX); } +bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags) +{ + const LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE); + return (style & WS_CHILD) || (flags & Qt::FramelessWindowHint); +} + // Set the WS_EX_LAYERED flag on a HWND if required. This is required for // translucent backgrounds, not fully opaque windows and for // Qt::WindowTransparentForInput (in combination with WS_EX_TRANSPARENT). bool QWindowsWindow::setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity) { const LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE); + // Native children are frameless by nature, so check for that as well. const bool needsLayered = (flags & Qt::WindowTransparentForInput) - || (hasAlpha && (flags & Qt::FramelessWindowHint)) || opacity < 1.0; + || (hasAlpha && hasNoNativeFrame(hwnd, flags)) || opacity < 1.0; const bool isLayered = (exStyle & WS_EX_LAYERED); if (needsLayered != isLayered) { if (needsLayered) { @@ -492,7 +496,7 @@ static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bo { if (QWindowsWindow::setWindowLayered(hwnd, flags, hasAlpha, level)) { const BYTE alpha = BYTE(qRound(255.0 * level)); - if (hasAlpha && !accelerated && (flags & Qt::FramelessWindowHint)) { + if (hasAlpha && !accelerated && QWindowsWindow::hasNoNativeFrame(hwnd, flags)) { // Non-GL windows with alpha: Use blend function to update. BLENDFUNCTION blend = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; UpdateLayeredWindow(hwnd, nullptr, nullptr, nullptr, nullptr, nullptr, 0, &blend, ULW_ALPHA); @@ -835,6 +839,10 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag // NOTE: WS_EX_TRANSPARENT flag can make mouse inputs fall through a layered window if (flagsIn & Qt::WindowTransparentForInput) exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT; + + // Currently only compatible with D3D surfaces, use it with care. + if (qEnvironmentVariableIntValue("QT_QPA_DISABLE_REDIRECTION_SURFACE")) + exStyle |= WS_EX_NOREDIRECTIONBITMAP; } } @@ -842,10 +850,17 @@ static inline bool shouldApplyDarkFrame(const QWindow *w) { if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) return false; - // the application has explicitly opted out of dark frames + + // the user of the application has explicitly opted out of dark frames if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) return false; + // the application explicitly overrides the color scheme + if (const auto requestedColorScheme = QWindowsTheme::instance()->requestedColorScheme(); + requestedColorScheme != Qt::ColorScheme::Unknown) { + return requestedColorScheme == Qt::ColorScheme::Dark; + } + // if the application supports a dark border, and the palette is dark (window background color // is darker than the text), then turn dark-border support on, otherwise use a light border. auto *dWindow = QWindowPrivate::get(const_cast<QWindow*>(w)); @@ -922,7 +937,7 @@ QWindowsWindowData return result; } - if (QWindowsContext::isDarkMode() && shouldApplyDarkFrame(w)) + if (QWindowsTheme::instance()->colorScheme() == Qt::ColorScheme::Dark && shouldApplyDarkFrame(w)) QWindowsWindow::setDarkBorderToWindow(result.hwnd, true); if (mirrorParentWidth != 0) { @@ -933,6 +948,7 @@ QWindowsWindowData QRect obtainedGeometry(context->obtainedPos, context->obtainedSize); result.geometry = obtainedGeometry; + result.restoreGeometry = frameGeometry(result.hwnd, topLevel); result.fullFrameMargins = context->margins; result.embedded = embedded; result.hasFrame = hasFrame; @@ -1007,6 +1023,21 @@ static QSize toNativeSizeConstrained(QSize dip, const QScreen *s) return dip; } +// Helper for checking if frame adjustment needs to be skipped +// NOTE: Unmaximized frameless windows will skip margins calculation +static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, DWORD style) +{ + return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE); +} + +// Helper for checking if frame adjustment needs to be skipped +// NOTE: Unmaximized frameless windows will skip margins calculation +static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, HWND hwnd) +{ + DWORD style = hwnd != nullptr ? DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)) : 0; + return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE); +} + /*! \class QWindowsGeometryHint \brief Stores geometry constraints and provides utility functions. @@ -1019,7 +1050,7 @@ static QSize toNativeSizeConstrained(QSize dip, const QScreen *s) QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD style, DWORD exStyle) { - if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) + if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style)) return {}; RECT rect = {0,0,0,0}; style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs. @@ -1035,15 +1066,13 @@ QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD styl QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, HWND hwnd) { - if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) - return {}; return frameOnPrimaryScreen(w, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)), DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE))); } QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyle, qreal dpi) { - if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) + if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style)) return {}; RECT rect = {0,0,0,0}; style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs. @@ -1061,7 +1090,7 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyl QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd, DWORD style, DWORD exStyle) { - if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) + if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style)) return {}; if (QWindowsScreenManager::isSingleScreen()) return frameOnPrimaryScreen(w, style, exStyle); @@ -1075,8 +1104,6 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd, DWORD style, D QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd) { - if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) - return {}; return frame(w, hwnd, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)), DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE))); } @@ -1085,7 +1112,7 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd) QMargins QWindowsGeometryHint::frame(const QWindow *w, const QRect &geometry, DWORD style, DWORD exStyle) { - if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) + if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style)) return {}; if (QWindowsScreenManager::isSingleScreen() || !QWindowsContext::shouldHaveNonClientDpiScaling(w)) { @@ -1338,6 +1365,8 @@ QWindowsForeignWindow::QWindowsForeignWindow(QWindow *window, HWND hwnd) , m_hwnd(hwnd) , m_topLevelStyle(0) { + if (QPlatformWindow::parent()) + setParent(QPlatformWindow::parent()); } void QWindowsForeignWindow::setParent(const QPlatformWindow *newParentWindow) @@ -1513,6 +1542,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) QWindowsWindow::~QWindowsWindow() { setFlag(WithinDestroy); + QWindowsThemeCache::clearThemeCache(m_data.hwnd); if (testFlag(TouchRegistered)) UnregisterTouchWindow(m_data.hwnd); destroyWindow(); @@ -1954,6 +1984,12 @@ void QWindowsWindow::handleCompositionSettingsChanged() } } +qreal QWindowsWindow::dpiRelativeScale(const UINT dpi) const +{ + return QHighDpiScaling::roundScaleFactor(qreal(dpi) / QWindowsScreen::baseDpi) / + QHighDpiScaling::roundScaleFactor(qreal(savedDpi()) / QWindowsScreen::baseDpi); +} + void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *result) { // We want to keep QWindow's device independent size constant across the @@ -1961,10 +1997,9 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT * // by the change of DPI (e.g. 120 -> 144 = 1.2), also taking any scale // factor rounding into account. The win32 window size includes the margins; // add the margins for the new DPI to the window size. - const int dpi = int(wParam); - const qreal scale = QHighDpiScaling::roundScaleFactor(qreal(dpi) / QWindowsScreen::baseDpi) / - QHighDpiScaling::roundScaleFactor(qreal(savedDpi()) / QWindowsScreen::baseDpi); - const QMargins margins = QWindowsGeometryHint::frame(window(), style(), exStyle(), dpi); + const UINT dpi = UINT(wParam); + const qreal scale = dpiRelativeScale(dpi); + const QMargins margins = fullFrameMargins(); if (!(m_data.flags & Qt::FramelessWindowHint)) { // We need to update the custom margins to match the current DPI, because // we don't want our users manually hook into this message just to set a @@ -1973,7 +2008,8 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT * // are currently doing. m_data.customMargins *= scale; } - const QSize windowSize = (geometry().size() * scale).grownBy(margins + customMargins()); + + const QSize windowSize = (geometry().size() * scale).grownBy((margins * scale) + customMargins()); SIZE *size = reinterpret_cast<SIZE *>(lParam); size->cx = windowSize.width(); size->cy = windowSize.height(); @@ -1983,11 +2019,17 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT * void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) { const UINT dpi = HIWORD(wParam); + const qreal scale = dpiRelativeScale(dpi); setSavedDpi(dpi); + QWindowsThemeCache::clearThemeCache(hwnd); + // Send screen change first, so that the new screen is set during any following resize checkForScreenChanged(QWindowsWindow::FromDpiChange); + if (!IsZoomed(hwnd)) + m_data.restoreGeometry.setSize(m_data.restoreGeometry.size() * scale); + // We get WM_DPICHANGED in one of two situations: // // 1. The DPI change is a "spontaneous" DPI change as a result of e.g. @@ -2010,13 +2052,22 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top, prcNewWindow->right - prcNewWindow->left, prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE); + // If the window does not have a frame, WM_MOVE and WM_SIZE won't be + // called which prevents the content from being scaled appropriately + // after a DPI change. + if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd)) + handleGeometryChange(); } + + // Re-apply mask now that we have a new DPI, which have resulted in + // a new scale factor. + setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window())); } void QWindowsWindow::handleDpiChangedAfterParent(HWND hwnd) { const UINT dpi = GetDpiForWindow(hwnd); - const qreal scale = qreal(dpi) / qreal(savedDpi()); + const qreal scale = dpiRelativeScale(dpi); setSavedDpi(dpi); checkForScreenChanged(QWindowsWindow::FromDpiChange); @@ -2220,10 +2271,10 @@ void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode) return; // For screens with different DPI: postpone until WM_DPICHANGE // Check on currentScreen as it can be 0 when resuming a session (QTBUG-80436). - if (mode == FromGeometryChange && currentScreen != nullptr - && !equalDpi(currentScreen->logicalDpi(), newScreen->logicalDpi())) { + const bool changingDpi = !equalDpi(QDpi(savedDpi(), savedDpi()), newScreen->logicalDpi()); + if (mode == FromGeometryChange && currentScreen != nullptr && changingDpi) return; - } + qCDebug(lcQpaWindow).noquote().nospace() << __FUNCTION__ << ' ' << window() << " \"" << (currentScreen ? currentScreen->name() : QString()) << "\"->\"" << newScreen->name() << '"'; @@ -2235,6 +2286,7 @@ void QWindowsWindow::handleGeometryChange() { const QRect previousGeometry = m_data.geometry; m_data.geometry = geometry_sys(); + updateFullFrameMargins(); QWindowSystemInterface::handleGeometryChange(window(), m_data.geometry); // QTBUG-32121: OpenGL/normal windows (with exception of ANGLE // which we no longer support in Qt 6) do not receive expose @@ -2252,6 +2304,9 @@ void QWindowsWindow::handleGeometryChange() if (testFlag(SynchronousGeometryChangeEvent)) QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); + if (!testFlag(ResizeMoveActive)) + updateRestoreGeometry(); + if (!wasSync) clearFlag(SynchronousGeometryChangeEvent); qCDebug(lcQpaEvents) << __FUNCTION__ << this << window() << m_data.geometry; @@ -2340,23 +2395,9 @@ static inline bool isSoftwareGl() } bool QWindowsWindow::handleWmPaint(HWND hwnd, UINT message, - WPARAM wParam, LPARAM, LRESULT *result) + WPARAM, LPARAM, LRESULT *result) { if (message == WM_ERASEBKGND) { // Backing store - ignored. - if (!m_firstBgDraw) { - // Get window background from the default palette; this will - // usually be the system background color. - const QColor bgColor = QGuiApplication::palette().color(QPalette::Window); - HBRUSH bgBrush = CreateSolidBrush(RGB(bgColor.red(), bgColor.green(), bgColor.blue())); - // Fill rectangle with system background color - RECT rc; - auto hdc = reinterpret_cast<HDC>(wParam); - GetClientRect(hwnd, &rc); - FillRect(hdc, &rc, bgBrush); - DeleteObject(bgBrush); - // Brush the window with system background color only for first time - m_firstBgDraw = true; - } *result = 1; return true; } @@ -2441,6 +2482,14 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state) handleHidden(); QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // Tell QQuickWindow to stop rendering now. } else { + if (state & Qt::WindowMaximized) { + WINDOWPLACEMENT windowPlacement{}; + windowPlacement.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(m_data.hwnd, &windowPlacement); + const RECT geometry = RECTfromQRect(m_data.restoreGeometry); + windowPlacement.rcNormalPosition = geometry; + SetWindowPlacement(m_data.hwnd, &windowPlacement); + } // QTBUG-17548: We send expose events when receiving WM_Paint, but for // layered windows and transient children, we won't receive any WM_Paint. QWindow *w = window(); @@ -2464,6 +2513,11 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state) } } +void QWindowsWindow::updateRestoreGeometry() +{ + m_data.restoreGeometry = normalFrameGeometry(m_data.hwnd); +} + void QWindowsWindow::setWindowState(Qt::WindowStates state) { if (m_data.hwnd) { @@ -2531,26 +2585,26 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState) if (testFlag(HasBorderInFullScreen)) newStyle |= WS_BORDER; setStyle(newStyle); - // Use geometry of QWindow::screen() within creation or the virtual screen the - // window is in (QTBUG-31166, QTBUG-30724). - const QScreen *screen = window()->screen(); - if (!screen) - screen = QGuiApplication::primaryScreen(); - const QRect r = screen ? QHighDpi::toNativePixels(screen->geometry(), window()) : m_savedFrameGeometry; - + const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo = {}; + monitorInfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfoW(monitor, &monitorInfo); + const QRect screenGeometry(monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top, + monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left, + monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); if (newState & Qt::WindowMinimized) { - setMinimizedGeometry(m_data.hwnd, r); + setMinimizedGeometry(m_data.hwnd, screenGeometry); if (stateChange & Qt::WindowMaximized) setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized); } else { const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE; const bool wasSync = testFlag(SynchronousGeometryChangeEvent); setFlag(SynchronousGeometryChangeEvent); - SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf); + SetWindowPos(m_data.hwnd, HWND_TOP, screenGeometry.left(), screenGeometry.top(), screenGeometry.width(), screenGeometry.height(), swpf); if (!wasSync) clearFlag(SynchronousGeometryChangeEvent); clearFlag(MaximizeToFullScreen); - QWindowSystemInterface::handleGeometryChange(window(), r); + QWindowSystemInterface::handleGeometryChange(window(), screenGeometry); QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); } } else { @@ -2643,7 +2697,7 @@ bool QWindowsWindow::windowEvent(QEvent *event) { switch (event->type()) { case QEvent::ApplicationPaletteChange: - setDarkBorder(QWindowsContext::isDarkMode()); + setDarkBorder(QWindowsTheme::instance()->colorScheme() == Qt::ColorScheme::Dark); break; case QEvent::WindowBlocked: // Blocked by another modal window. setEnabled(false); @@ -2670,10 +2724,17 @@ void QWindowsWindow::propagateSizeHints() bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &margins) { auto *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam); + const QRect suggestedFrameGeometry(windowPos->x, windowPos->y, + windowPos->cx, windowPos->cy); + const QRect suggestedGeometry = suggestedFrameGeometry - margins; // Tell Windows to discard the entire contents of the client area, as re-using // parts of the client area would lead to jitter during resize. - windowPos->flags |= SWP_NOCOPYBITS; + // Check the suggestedGeometry against the current one to only discard during + // resize, and not a plain move. We also look for SWP_NOSIZE since that, too, + // implies an identical size, and comparing QRects wouldn't work with null cx/cy + if (!(windowPos->flags & SWP_NOSIZE) && suggestedGeometry.size() != qWindow->geometry().size()) + windowPos->flags |= SWP_NOCOPYBITS; if ((windowPos->flags & SWP_NOZORDER) == 0) { if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(qWindow)) { @@ -2689,9 +2750,6 @@ bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow * return false; if (windowPos->flags & SWP_NOSIZE) return false; - const QRect suggestedFrameGeometry(windowPos->x, windowPos->y, - windowPos->cx, windowPos->cy); - const QRect suggestedGeometry = suggestedFrameGeometry - margins; const QRectF correctedGeometryF = QPlatformWindow::closestAcceptableGeometry(qWindow, suggestedGeometry); if (!correctedGeometryF.isValid()) return false; @@ -2713,7 +2771,7 @@ bool QWindowsWindow::handleGeometryChanging(MSG *message) const void QWindowsWindow::setFullFrameMargins(const QMargins &newMargins) { - if (m_data.flags & Qt::FramelessWindowHint) + if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd)) return; if (m_data.fullFrameMargins != newMargins) { qCDebug(lcQpaWindow) << __FUNCTION__ << window() << m_data.fullFrameMargins << "->" << newMargins; @@ -2732,14 +2790,46 @@ void QWindowsWindow::updateFullFrameMargins() void QWindowsWindow::calculateFullFrameMargins() { - if (m_data.flags & Qt::FramelessWindowHint) + if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd)) + return; + + // QTBUG-113736: systemMargins depends on AdjustWindowRectExForDpi. This doesn't take into + // account possible external modifications to the titlebar, as with ExtendsContentIntoTitleBar() + // from the Windows App SDK. We can fix this by comparing the WindowRect (which includes the + // frame) to the ClientRect. If a 'typical' frame is detected, i.e. only the titlebar has been + // modified, we can safely adjust the frame by deducting the bottom margin to the total Y + // difference between the two rects, to get the actual size of the titlebar and prevent + // unwanted client area slicing. + + RECT windowRect{}; + RECT clientRect{}; + GetWindowRect(handle(), &windowRect); + GetClientRect(handle(), &clientRect); + + // QTBUG-117704 It is also possible that the user has manually removed the frame (for example + // by handling WM_NCCALCSIZE). If that is the case, i.e., the client area and the window area + // have identical sizes, we don't want to override the user-defined margins. + + if (qrectFromRECT(windowRect).size() == qrectFromRECT(clientRect).size()) return; + // Normally obtained from WM_NCCALCSIZE. This calculation only works // when no native menu is present. const auto systemMargins = testFlag(DisableNonClientScaling) ? QWindowsGeometryHint::frameOnPrimaryScreen(window(), m_data.hwnd) : frameMargins_sys(); - setFullFrameMargins(systemMargins + customMargins()); + const QMargins actualMargins = systemMargins + customMargins(); + + const int yDiff = (windowRect.bottom - windowRect.top) - (clientRect.bottom - clientRect.top); + const bool typicalFrame = (actualMargins.left() == actualMargins.right()) + && (actualMargins.right() == actualMargins.bottom()); + + const QMargins adjustedMargins = typicalFrame ? + QMargins(actualMargins.left(), (yDiff - actualMargins.bottom()), + actualMargins.right(), actualMargins.bottom()) + : actualMargins; + + setFullFrameMargins(adjustedMargins); } QMargins QWindowsWindow::frameMargins() const @@ -2752,7 +2842,7 @@ QMargins QWindowsWindow::frameMargins() const QMargins QWindowsWindow::fullFrameMargins() const { - if (m_data.flags & Qt::FramelessWindowHint) + if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd)) return {}; return m_data.fullFrameMargins; } @@ -3203,17 +3293,6 @@ enum : WORD { DwmwaUseImmersiveDarkModeBefore20h1 = 19 }; -static bool queryDarkBorder(HWND hwnd) -{ - BOOL result = FALSE; - const bool ok = - SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &result, sizeof(result))) - || SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &result, sizeof(result))); - if (!ok) - qCWarning(lcQpaWindow, "%s: Unable to retrieve dark window border setting.", __FUNCTION__); - return result == TRUE; -} - bool QWindowsWindow::setDarkBorderToWindow(HWND hwnd, bool d) { const BOOL darkBorder = d ? TRUE : FALSE; @@ -3229,8 +3308,6 @@ void QWindowsWindow::setDarkBorder(bool d) { // respect explicit opt-out and incompatible palettes or styles d = d && shouldApplyDarkFrame(window()); - if (queryDarkBorder(m_data.hwnd) == d) - return; setDarkBorderToWindow(m_data.hwnd, d); } @@ -3359,24 +3436,6 @@ void QWindowsWindow::registerTouchWindow() qErrnoWarning("RegisterTouchWindow() failed for window '%s'.", qPrintable(window()->objectName())); } -void QWindowsWindow::aboutToMakeCurrent() -{ -#ifndef QT_NO_OPENGL - // For RasterGLSurface windows, that become OpenGL windows dynamically, it might be - // time to set up some GL specifics. This is particularly important for layered - // windows (WS_EX_LAYERED due to alpha > 0). - const bool isCompositing = qt_window_private(window())->compositing; - if (isCompositing != testFlag(Compositing)) { - if (isCompositing) - setFlag(Compositing); - else - clearFlag(Compositing); - - updateGLWindowSettings(window(), m_data.hwnd, m_data.flags, m_opacity); - } -#endif -} - void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border) { if (QPlatformWindow *handle = window->handle()) diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 38e9fb9a5b..024711e7f3 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -78,6 +78,7 @@ struct QWindowsWindowData { Qt::WindowFlags flags; QRect geometry; + QRect restoreGeometry; QMargins fullFrameMargins; // Do not use directly for windows, see FrameDirty. QMargins customMargins; // User-defined, additional frame for NCCALCSIZE HWND hwnd = nullptr; @@ -218,6 +219,8 @@ public: void setGeometry(const QRect &rect) override; QRect geometry() const override { return m_data.geometry; } QRect normalGeometry() const override; + QRect restoreGeometry() const { return m_data.restoreGeometry; } + void updateRestoreGeometry(); void setVisible(bool visible) override; bool isVisible() const; @@ -296,6 +299,7 @@ public: static inline void *userDataOf(HWND hwnd); static inline void setUserDataOf(HWND hwnd, void *ud); + static bool hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags); static bool setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity); bool isLayered() const; @@ -320,7 +324,6 @@ public: void *surface(void *nativeConfig, int *err); void invalidateSurface() override; - void aboutToMakeCurrent(); void setAlertState(bool enabled) override; bool isAlertState() const override { return testFlag(AlertState); } @@ -342,6 +345,7 @@ public: void setSavedDpi(int dpi) { m_savedDpi = dpi; } int savedDpi() const { return m_savedDpi; } + qreal dpiRelativeScale(const UINT dpi) const; private: inline void show_sys() const; @@ -377,7 +381,6 @@ private: HICON m_iconBig = nullptr; void *m_surface = nullptr; int m_savedDpi = 96; - bool m_firstBgDraw = false; static bool m_screenForGLInitialized; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp index 001cb8505b..1abb412ccd 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp @@ -5,6 +5,7 @@ #if QT_CONFIG(accessibility) #include "qwindowsuiaaccessibility.h" +#include "qwindowsuiautomation.h" #include "qwindowsuiamainprovider.h" #include "qwindowsuiautils.h" @@ -14,13 +15,13 @@ #include <QtGui/private/qguiapplication_p.h> #include <QtCore/qt_windows.h> #include <qpa/qplatformintegration.h> -#include <QtGui/private/qwindowsuiawrapper_p.h> #include <QtCore/private/qwinregistry_p.h> QT_BEGIN_NAMESPACE using namespace QWindowsUiAutomation; +using namespace Qt::Literals::StringLiterals; bool QWindowsUiaAccessibility::m_accessibleActive = false; @@ -46,7 +47,7 @@ bool QWindowsUiaAccessibility::handleWmGetObject(HWND hwnd, WPARAM wParam, LPARA if (QWindow *window = QWindowsContext::instance()->findWindow(hwnd)) { if (QAccessibleInterface *accessible = window->accessibleRoot()) { QWindowsUiaMainProvider *provider = QWindowsUiaMainProvider::providerForAccessible(accessible); - *lResult = QWindowsUiaWrapper::instance()->returnRawElementProvider(hwnd, wParam, lParam, provider); + *lResult = UiaReturnRawElementProvider(hwnd, wParam, lParam, provider); return true; } } @@ -78,8 +79,8 @@ static QString alertSound(const QObject *object) static QString soundFileName(const QString &soundName) { - const QString key = QStringLiteral("AppEvents\\Schemes\\Apps\\.Default\\") - + soundName + QStringLiteral("\\.Current"); + const QString key = "AppEvents\\Schemes\\Apps\\.Default\\"_L1 + + soundName + "\\.Current"_L1; return QWinRegistryKey(HKEY_CURRENT_USER, key).stringValue(L""); } @@ -97,11 +98,7 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event if (!event) return; - // Ignore events sent before the first UI Automation - // request or while QAccessible is being activated. - if (!m_accessibleActive) - return; - + // Always handle system sound events switch (event->type()) { case QAccessible::PopupMenuStart: playSystemSound(QStringLiteral("MenuPopup")); @@ -116,16 +113,17 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event break; } - QAccessibleInterface *accessible = event->accessibleInterface(); - if (!isActive() || !accessible || !accessible->isValid()) + // Ignore events sent before the first UI Automation + // request or while QAccessible is being activated. + if (!m_accessibleActive) return; - // Ensures QWindowsUiaWrapper is properly initialized. - if (!QWindowsUiaWrapper::instance()->ready()) + QAccessibleInterface *accessible = event->accessibleInterface(); + if (!isActive() || !accessible || !accessible->isValid()) return; // No need to do anything when nobody is listening. - if (!QWindowsUiaWrapper::instance()->clientsAreListening()) + if (!UiaClientsAreListening()) return; switch (event->type()) { diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h index 1813bb4d89..2e8ee585da 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h @@ -7,6 +7,7 @@ #include <QtGui/qtguiglobal.h> #if QT_CONFIG(accessibility) +#include <QtCore/qt_windows.h> #include "qwindowscontext.h" #include <qpa/qplatformaccessibility.h> diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h index c899b4096e..032679ab10 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h @@ -10,8 +10,8 @@ #include <QtGui/qaccessible.h> #include <QtCore/qpointer.h> -#include <qwindowscombase.h> -#include <QtGui/private/qwindowsuiawrapper_p.h> +#include "qwindowsuiautomation.h" +#include <QtCore/private/qcomobject_p.h> QT_BEGIN_NAMESPACE diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h index 49b37ba295..b384eb521c 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE // Implements the Expand/Collapse control pattern provider. Used for menu items with submenus. class QWindowsUiaExpandCollapseProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IExpandCollapseProvider> + public QComObject<IExpandCollapseProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaExpandCollapseProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h index b501616966..289a867869 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE // Implements the Grid Item control pattern provider. Used by items within a table/tree. class QWindowsUiaGridItemProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IGridItemProvider> + public QComObject<IGridItemProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaGridItemProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h index b905133f6b..d33bbd0429 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h @@ -12,8 +12,7 @@ QT_BEGIN_NAMESPACE // Implements the Grid control pattern provider. Used by tables/trees. -class QWindowsUiaGridProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IGridProvider> +class QWindowsUiaGridProvider : public QWindowsUiaBaseProvider, public QComObject<IGridProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaGridProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h index eca7e73039..ec006c673e 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h @@ -12,8 +12,7 @@ QT_BEGIN_NAMESPACE // Implements the Invoke control pattern provider. -class QWindowsUiaInvokeProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IInvokeProvider> +class QWindowsUiaInvokeProvider : public QWindowsUiaBaseProvider, public QComObject<IInvokeProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaInvokeProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index 16f0a3f9a6..95ddbcced6 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -18,7 +18,6 @@ #include "qwindowsuiagriditemprovider.h" #include "qwindowsuiawindowprovider.h" #include "qwindowsuiaexpandcollapseprovider.h" -#include "qwindowscombase.h" #include "qwindowscontext.h" #include "qwindowsuiautils.h" #include "qwindowsuiaprovidercache.h" @@ -27,6 +26,7 @@ #include <QtGui/qaccessible.h> #include <QtGui/qguiapplication.h> #include <QtGui/qwindow.h> +#include <qpa/qplatforminputcontextfactory_p.h> #if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) #include <comdef.h> @@ -61,9 +61,8 @@ QWindowsUiaMainProvider *QWindowsUiaMainProvider::providerForAccessible(QAccessi return provider; } -QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount) - : QWindowsUiaBaseProvider(QAccessible::uniqueId(a)), - m_ref(initialRefCount) +QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a) + : QWindowsUiaBaseProvider(QAccessible::uniqueId(a)) { } @@ -80,7 +79,7 @@ void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event) accessible = child; } if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId); } } @@ -97,7 +96,7 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve if (accessible->state().checked) toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On; setVariantI4(toggleState, &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal); } } } @@ -106,13 +105,13 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve // Notifies window opened/closed. if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { if (accessible->state().active) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId); + UiaRaiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId); if (QAccessibleInterface *focused = accessible->focusChild()) { if (QWindowsUiaMainProvider *focusedProvider = providerForAccessible(focused)) - QWindowsUiaWrapper::instance()->raiseAutomationEvent(focusedProvider, UIA_AutomationFocusChangedEventId); + UiaRaiseAutomationEvent(focusedProvider, UIA_AutomationFocusChangedEventId); } } else { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowClosedEventId); + UiaRaiseAutomationEvent(provider, UIA_Window_WindowClosedEventId); } } } @@ -141,27 +140,11 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve } if (event->value().typeId() == QMetaType::QString) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { - - // Tries to notify the change using UiaRaiseNotificationEvent(), which is only available on - // Windows 10 version 1709 or newer. Otherwise uses UiaRaiseAutomationPropertyChangedEvent(). - - BSTR displayString = bStrFromQString(event->value().toString()); - BSTR activityId = bStrFromQString(QString()); - - HRESULT hr = QWindowsUiaWrapper::instance()->raiseNotificationEvent(provider, NotificationKind_Other, - NotificationProcessing_ImportantMostRecent, - displayString, activityId); - - ::SysFreeString(displayString); - ::SysFreeString(activityId); - - if (hr == static_cast<HRESULT>(UIA_E_NOTSUPPORTED)) { - VARIANT oldVal, newVal; - clearVariant(&oldVal); - setVariantString(event->value().toString(), &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal); - ::SysFreeString(newVal.bstrVal); - } + // Notifies changes in string values. + VARIANT oldVal, newVal; + clearVariant(&oldVal); + setVariantString(event->value().toString(), &newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal); } } else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { @@ -169,7 +152,7 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve VARIANT oldVal, newVal; clearVariant(&oldVal); setVariantDouble(valueInterface->currentValue().toDouble(), &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal); } } } @@ -185,7 +168,7 @@ void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event) VARIANT oldVal, newVal; clearVariant(&oldVal); setVariantString(accessible->text(QAccessible::Name), &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal); ::SysFreeString(newVal.bstrVal); } } @@ -196,7 +179,7 @@ void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId); + UiaRaiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId); } } } @@ -208,13 +191,13 @@ void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event) if (accessible->textInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { if (event->type() == QAccessible::TextSelectionChanged) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); } else if (event->type() == QAccessible::TextCaretMoved) { if (!accessible->state().readOnly) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); } } else { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_Text_TextChangedEventId); } } } @@ -223,33 +206,26 @@ void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event) HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface) { - if (!iface) - return E_INVALIDARG; - *iface = nullptr; - - QAccessibleInterface *accessible = accessibleInterface(); + HRESULT result = QComObject::QueryInterface(iid, iface); - const bool result = qWindowsComQueryUnknownInterfaceMulti<IRawElementProviderSimple>(this, iid, iface) - || qWindowsComQueryInterface<IRawElementProviderSimple>(this, iid, iface) - || qWindowsComQueryInterface<IRawElementProviderFragment>(this, iid, iface) - || (accessible && hwndForAccessible(accessible) && qWindowsComQueryInterface<IRawElementProviderFragmentRoot>(this, iid, iface)); - return result ? S_OK : E_NOINTERFACE; -} + if (SUCCEEDED(result) && iid == __uuidof(IRawElementProviderFragmentRoot)) { + QAccessibleInterface *accessible = accessibleInterface(); + if (accessible && hwndForAccessible(accessible)) { + result = S_OK; + } else { + result = E_NOINTERFACE; + iface = nullptr; + } + } -ULONG QWindowsUiaMainProvider::AddRef() -{ - return ++m_ref; + return result; } ULONG STDMETHODCALLTYPE QWindowsUiaMainProvider::Release() { QMutexLocker locker(&m_mutex); - if (!--m_ref) { - delete this; - return 0; - } - return m_ref; + return QComObject::Release(); } HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal) @@ -304,15 +280,18 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow *pRetVal = new QWindowsUiaToggleProvider(id()); break; case UIA_SelectionPatternId: - // Lists of items. - if (accessible->role() == QAccessible::List + case UIA_SelectionPattern2Id: + // Selections via QAccessibleSelectionInterface or lists of items. + if (accessible->selectionInterface() + || accessible->role() == QAccessible::List || accessible->role() == QAccessible::PageTabList) { *pRetVal = new QWindowsUiaSelectionProvider(id()); } break; case UIA_SelectionItemPatternId: - // Items within a list and radio buttons. - if ((accessible->role() == QAccessible::RadioButton) + // Parent supports selection interface or items within a list and radio buttons. + if ((accessible->parent() && accessible->parent()->selectionInterface()) + || (accessible->role() == QAccessible::RadioButton) || (accessible->role() == QAccessible::ListItem) || (accessible->role() == QAccessible::PageTab)) { *pRetVal = new QWindowsUiaSelectionItemProvider(id()); @@ -369,6 +348,28 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow return S_OK; } +void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* accessible, + QAccessible::Relation relation, VARIANT *pRetVal) +{ + Q_ASSERT(accessible); + + typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair; + const QList<RelationPair> relationInterfaces = accessible->relations(relation); + if (relationInterfaces.empty()) + return; + + SAFEARRAY *elements = SafeArrayCreateVector(VT_UNKNOWN, 0, relationInterfaces.size()); + for (LONG i = 0; i < relationInterfaces.size(); ++i) { + if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(relationInterfaces.at(i).first)) { + SafeArrayPutElement(elements, &i, static_cast<IRawElementProviderSimple*>(childProvider)); + childProvider->Release(); + } + } + + pRetVal->vt = VT_UNKNOWN | VT_ARRAY; + pRetVal->parray = elements; +} + HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp; @@ -403,6 +404,15 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR setVariantString(className, pRetVal); } break; + case UIA_DescribedByPropertyId: + fillVariantArrayForRelation(accessible, QAccessible::DescriptionFor, pRetVal); + break; + case UIA_FlowsFromPropertyId: + fillVariantArrayForRelation(accessible, QAccessible::FlowsTo, pRetVal); + break; + case UIA_FlowsToPropertyId: + fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal); + break; case UIA_FrameworkIdPropertyId: setVariantString(QStringLiteral("Qt"), pRetVal); break; @@ -416,7 +426,7 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR // The native OSK should be disabled if the Qt OSK is in use, // or if disabled via application attribute. - static bool imModuleEmpty = qEnvironmentVariableIsEmpty("QT_IM_MODULE"); + static bool imModuleEmpty = QPlatformInputContextFactory::requested().isEmpty(); bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard); // If we want to disable the native OSK auto-showing @@ -519,7 +529,7 @@ HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderS // Returns a host provider only for controls associated with a native window handle. Others should return NULL. if (QAccessibleInterface *accessible = accessibleInterface()) { if (HWND hwnd = hwndForAccessible(accessible)) { - return QWindowsUiaWrapper::instance()->hostProviderFromHwnd(hwnd, pRetVal); + return UiaHostProviderFromHwnd(hwnd, pRetVal); } } return S_OK; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h index fb5069f620..99db0ed318 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h @@ -20,15 +20,13 @@ QT_BEGIN_NAMESPACE // The main UI Automation class. class QWindowsUiaMainProvider : public QWindowsUiaBaseProvider, - public IRawElementProviderSimple, - public IRawElementProviderFragment, - public IRawElementProviderFragmentRoot + public QComObject<IRawElementProviderSimple, IRawElementProviderFragment, IRawElementProviderFragmentRoot> { Q_OBJECT Q_DISABLE_COPY_MOVE(QWindowsUiaMainProvider) public: static QWindowsUiaMainProvider *providerForAccessible(QAccessibleInterface *accessible); - explicit QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount = 1); + explicit QWindowsUiaMainProvider(QAccessibleInterface *a); virtual ~QWindowsUiaMainProvider(); static void notifyFocusChange(QAccessibleEvent *event); static void notifyStateChange(QAccessibleStateChangeEvent *event); @@ -39,7 +37,6 @@ public: // IUnknown HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override; - ULONG STDMETHODCALLTYPE AddRef() override; ULONG STDMETHODCALLTYPE Release() override; // IRawElementProviderSimple methods @@ -62,7 +59,7 @@ public: private: QString automationIdForAccessible(const QAccessibleInterface *accessible); - ULONG m_ref; + static void fillVariantArrayForRelation(QAccessibleInterface *accessible, QAccessible::Relation relation, VARIANT *pRetVal); static QMutex m_mutex; }; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h index 3e06961a82..ffb5ae155b 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE // Implements the Range Value control pattern provider. class QWindowsUiaRangeValueProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IRangeValueProvider> + public QComObject<IRangeValueProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaRangeValueProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp index 7e829b24a7..95bc2f7570 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp @@ -36,6 +36,14 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::Select() if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; + if (QAccessibleInterface *parent = accessible->parent()) { + if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) { + selectionInterface->clear(); + bool ok = selectionInterface->select(accessible); + return ok ? S_OK : S_FALSE; + } + } + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); if (!actionInterface) return UIA_E_ELEMENTNOTAVAILABLE; @@ -73,6 +81,13 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::AddToSelection() if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; + if (QAccessibleInterface *parent = accessible->parent()) { + if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) { + bool ok = selectionInterface->select(accessible); + return ok ? S_OK : S_FALSE; + } + } + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); if (!actionInterface) return UIA_E_ELEMENTNOTAVAILABLE; @@ -98,6 +113,13 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::RemoveFromSelection( if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; + if (QAccessibleInterface *parent = accessible->parent()) { + if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) { + bool ok = selectionInterface->unselect(accessible); + return ok ? S_OK : S_FALSE; + } + } + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); if (!actionInterface) return UIA_E_ELEMENTNOTAVAILABLE; @@ -124,6 +146,14 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_IsSelected(BOOL if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; + if (QAccessibleInterface *parent = accessible->parent()) { + if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) { + bool selected = selectionInterface->isSelected(accessible); + *pRetVal = selected ? TRUE : FALSE; + return S_OK; + } + } + if (accessible->role() == QAccessible::RadioButton) *pRetVal = accessible->state().checked; else if (accessible->role() == QAccessible::PageTab) @@ -146,12 +176,18 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_SelectionContain if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; + QAccessibleInterface *parent = accessible->parent(); + if (parent && parent->selectionInterface()) { + *pRetVal = QWindowsUiaMainProvider::providerForAccessible(parent); + return S_OK; + } + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); if (!actionInterface) return UIA_E_ELEMENTNOTAVAILABLE; // Radio buttons do not require a container. - if (QAccessibleInterface *parent = accessible->parent()) { + if (parent) { if ((accessible->role() == QAccessible::ListItem && parent->role() == QAccessible::List) || (accessible->role() == QAccessible::PageTab && parent->role() == QAccessible::PageTabList)) { *pRetVal = QWindowsUiaMainProvider::providerForAccessible(parent); diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h index b510ae951c..ee34fd9edd 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE // Implements the Selection Item control pattern provider. Used for List items and radio buttons. class QWindowsUiaSelectionItemProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<ISelectionItemProvider> + public QComObject<ISelectionItemProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaSelectionItemProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp index 45f3b20552..37148c655a 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp @@ -41,17 +41,23 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::GetSelection(SAFEARRAY * if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; - // First put selected items in a list, then build a safe array with the right size. + // First get/create list of selected items, then build a safe array with the right size. QList<QAccessibleInterface *> selectedList; - for (int i = 0; i < accessible->childCount(); ++i) { - if (QAccessibleInterface *child = accessible->child(i)) { - if (accessible->role() == QAccessible::PageTabList) { - if (child->role() == QAccessible::PageTab && child->state().focused) { - selectedList.append(child); - } - } else { - if (child->state().selected) { - selectedList.append(child); + if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) { + selectedList = selectionInterface->selectedItems(); + } else { + const int childCount = accessible->childCount(); + selectedList.reserve(childCount); + for (int i = 0; i < childCount; ++i) { + if (QAccessibleInterface *child = accessible->child(i)) { + if (accessible->role() == QAccessible::PageTabList) { + if (child->role() == QAccessible::PageTab && child->state().focused) { + selectedList.append(child); + } + } else { + if (child->state().selected) { + selectedList.append(child); + } } } } @@ -102,11 +108,15 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired( // Initially returns false if none are selected. After the first selection, it may be required. bool anySelected = false; - for (int i = 0; i < accessible->childCount(); ++i) { - if (QAccessibleInterface *child = accessible->child(i)) { - if (child->state().selected) { - anySelected = true; - break; + if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) { + anySelected = selectionInterface->selectedItem(0) != nullptr; + } else { + for (int i = 0; i < accessible->childCount(); ++i) { + if (QAccessibleInterface *child = accessible->child(i)) { + if (child->state().selected) { + anySelected = true; + break; + } } } } @@ -116,6 +126,134 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired( return S_OK; } +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleInterface *firstSelectedChild = nullptr; + if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) { + firstSelectedChild = selectionInterface->selectedItem(0); + if (!firstSelectedChild) + return UIA_E_ELEMENTNOTAVAILABLE; + } else { + int i = 0; + while (!firstSelectedChild && i < accessible->childCount()) { + if (QAccessibleInterface *child = accessible->child(i)) { + if (accessible->role() == QAccessible::PageTabList) { + if (child->role() == QAccessible::PageTab && child->state().focused) + firstSelectedChild = child; + } else if (child->state().selected) { + firstSelectedChild = child; + } + } + i++; + } + } + + if (!firstSelectedChild) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(firstSelectedChild)) + { + *pRetVal = static_cast<IRawElementProviderSimple *>(childProvider); + return S_OK; + } + + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleInterface *lastSelectedChild = nullptr; + if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) { + const int selectedItemCount = selectionInterface->selectedItemCount(); + if (selectedItemCount <= 0) + return UIA_E_ELEMENTNOTAVAILABLE; + lastSelectedChild = selectionInterface->selectedItem(selectedItemCount - 1); + } else { + int i = accessible->childCount() - 1; + while (!lastSelectedChild && i >= 0) { + if (QAccessibleInterface *child = accessible->child(i)) { + if (accessible->role() == QAccessible::PageTabList) { + if (child->role() == QAccessible::PageTab && child->state().focused) + lastSelectedChild = child; + } else if (child->state().selected) { + lastSelectedChild = child; + } + } + i--; + } + } + + if (!lastSelectedChild) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(lastSelectedChild)) + { + *pRetVal = static_cast<IRawElementProviderSimple *>(childProvider); + return S_OK; + } + + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + return get_FirstSelectedItem(pRetVal); +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_ItemCount(__RPC__out int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = -1; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + + if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) + *pRetVal = selectionInterface->selectedItemCount(); + else { + int selectedCount = 0; + for (int i = 0; i < accessible->childCount(); i++) { + if (QAccessibleInterface *child = accessible->child(i)) { + if (accessible->role() == QAccessible::PageTabList) { + if (child->role() == QAccessible::PageTab && child->state().focused) + selectedCount++; + } else if (child->state().selected) { + selectedCount++; + } + } + } + *pRetVal = selectedCount; + } + + return S_OK; +} + QT_END_NAMESPACE #endif // QT_CONFIG(accessibility) diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h index 9a187c9413..7a899e4261 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h @@ -11,9 +11,22 @@ QT_BEGIN_NAMESPACE +namespace QtPrivate { + +template <> +struct QComObjectTraits<ISelectionProvider2> +{ + static constexpr bool isGuidOf(REFIID riid) noexcept + { + return QComObjectTraits<ISelectionProvider2, ISelectionProvider>::isGuidOf(riid); + } +}; + +} // namespace QtPrivate + // Implements the Selection control pattern provider. Used for Lists. class QWindowsUiaSelectionProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<ISelectionProvider> + public QComObject<ISelectionProvider2> { Q_DISABLE_COPY_MOVE(QWindowsUiaSelectionProvider) public: @@ -24,6 +37,12 @@ public: HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal) override; HRESULT STDMETHODCALLTYPE get_CanSelectMultiple(BOOL *pRetVal) override; HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(BOOL *pRetVal) override; + + // ISelectionProvider2 + HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override; + HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override; + HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override; + HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *pRetVal) override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h index 8aed180671..3bb0e1027e 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE // Implements the Table Item control pattern provider. Used by items within a table/tree. class QWindowsUiaTableItemProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<ITableItemProvider> + public QComObject<ITableItemProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaTableItemProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h index 6454bb9441..8beb11decf 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h @@ -12,8 +12,7 @@ QT_BEGIN_NAMESPACE // Implements the Table control pattern provider. Used by tables/trees. -class QWindowsUiaTableProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<ITableProvider> +class QWindowsUiaTableProvider : public QWindowsUiaBaseProvider, public QComObject<ITableProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaTableProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp index e3b7c69b60..172836232e 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp @@ -26,20 +26,6 @@ QWindowsUiaTextProvider::~QWindowsUiaTextProvider() { } -HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::QueryInterface(REFIID iid, LPVOID *iface) -{ - qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; - - if (!iface) - return E_INVALIDARG; - *iface = nullptr; - - const bool result = qWindowsComQueryUnknownInterfaceMulti<ITextProvider>(this, iid, iface) - || qWindowsComQueryInterface<ITextProvider>(this, iid, iface) - || qWindowsComQueryInterface<ITextProvider2>(this, iid, iface); - return result ? S_OK : E_NOINTERFACE; -} - // Returns an array of providers for the selected text ranges. HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::GetSelection(SAFEARRAY **pRetVal) { @@ -146,9 +132,10 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::RangeFromPoint(UiaPoint point nativeUiaPointToPoint(point, window, &pt); int offset = textInterface->offsetAtPoint(pt); - if ((offset >= 0) && (offset < textInterface->characterCount())) { - *pRetVal = new QWindowsUiaTextRangeProvider(id(), offset, offset); - } + if (offset < 0 || offset >= textInterface->characterCount()) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = new QWindowsUiaTextRangeProvider(id(), offset, offset); return S_OK; } diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h index 5e6d430f1a..8f886510b4 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h @@ -12,18 +12,27 @@ QT_BEGIN_NAMESPACE +namespace QtPrivate { + +template <> +struct QComObjectTraits<ITextProvider2> +{ + static constexpr bool isGuidOf(REFIID riid) noexcept + { + return QComObjectTraits<ITextProvider2, ITextProvider>::isGuidOf(riid); + } +}; + +} // namespace QtPrivate + // Implements the Text control pattern provider. Used for text controls. -class QWindowsUiaTextProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<ITextProvider2> +class QWindowsUiaTextProvider : public QWindowsUiaBaseProvider, public QComObject<ITextProvider2> { Q_DISABLE_COPY_MOVE(QWindowsUiaTextProvider) public: explicit QWindowsUiaTextProvider(QAccessible::Id id); ~QWindowsUiaTextProvider(); - // IUnknown overrides - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override; - // ITextProvider HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal) override; HRESULT STDMETHODCALLTYPE GetVisibleRanges(SAFEARRAY **pRetVal) override; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp index 4d02036196..a62a33cfe2 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp @@ -167,6 +167,15 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTAT else setVariantI4(CaretPosition_Unknown, pRetVal); break; + case UIA_StrikethroughStyleAttributeId: + { + const QString value = valueForIA2Attribute(textInterface, QStringLiteral("text-line-through-type")); + if (value.isEmpty()) + break; + const TextDecorationLineStyle uiaLineStyle = uiaLineStyleForIA2LineStyle(value); + setVariantI4(uiaLineStyle, pRetVal); + break; + } default: break; } @@ -307,14 +316,14 @@ HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit, int count, int *pRetVa int len = textInterface->characterCount(); - if (len < 1) + if (len < 1 || count == 0) // MSDN: "Zero has no effect." return S_OK; if (unit == TextUnit_Character) { // Moves the start point, ensuring it lies within the bounds. - int start = qBound(0, m_startOffset + count, len - 1); + int start = qBound(0, m_startOffset + count, len); // If range was initially empty, leaves it as is; otherwise, normalizes it to one char. - m_endOffset = (m_endOffset > m_startOffset) ? start + 1 : start; + m_endOffset = (m_endOffset > m_startOffset) ? qMin(start + 1, len) : start; *pRetVal = start - m_startOffset; // Returns the actually moved distance. m_startOffset = start; } else { @@ -385,7 +394,7 @@ HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoin if (unit == TextUnit_Character) { if (endpoint == TextPatternRangeEndpoint_Start) { - int boundedValue = qBound(0, m_startOffset + count, len - 1); + int boundedValue = qBound(0, m_startOffset + count, len); *pRetVal = boundedValue - m_startOffset; m_startOffset = boundedValue; m_endOffset = qBound(m_startOffset, m_endOffset, len); @@ -517,6 +526,42 @@ HRESULT QWindowsUiaTextRangeProvider::unselect() return S_OK; } +// helper method to retrieve the value of the given IAccessible2 text attribute, +// or an empty string if not set +QString QWindowsUiaTextRangeProvider::valueForIA2Attribute(QAccessibleTextInterface *textInterface, + const QString &key) +{ + Q_ASSERT(textInterface); + + int startOffset; + int endOffset; + const QString attributes = textInterface->attributes(m_startOffset, &startOffset, &endOffset); + // don't report if attributes don't apply for the whole range + if (startOffset > m_startOffset || endOffset < m_endOffset) + return {}; + + for (auto attr : QStringTokenizer{attributes, u';'}) + { + const QList<QStringView> items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive); + if (items.count() == 2 && items[0] == key) + return items[1].toString(); + } + + return {}; +} + +TextDecorationLineStyle QWindowsUiaTextRangeProvider::uiaLineStyleForIA2LineStyle(const QString &ia2LineStyle) +{ + if (ia2LineStyle == QStringLiteral("none")) + return TextDecorationLineStyle_None; + if (ia2LineStyle == QStringLiteral("single")) + return TextDecorationLineStyle_Single; + if (ia2LineStyle == QStringLiteral("double")) + return TextDecorationLineStyle_Double; + + return TextDecorationLineStyle_Other; +} + QT_END_NAMESPACE #endif // QT_CONFIG(accessibility) diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h index f7d28a34d2..a37429a603 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE // Implements the Text Range control pattern provider. Used for text controls. class QWindowsUiaTextRangeProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<ITextRangeProvider> + public QComObject<ITextRangeProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaTextRangeProvider) public: @@ -42,6 +42,8 @@ public: private: HRESULT unselect(); + QString valueForIA2Attribute(QAccessibleTextInterface *textInterface, const QString &key); + TextDecorationLineStyle uiaLineStyleForIA2LineStyle(const QString &ia2LineStyle); int m_startOffset; int m_endOffset; }; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h index 742e26f6d6..17150dfbe0 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h @@ -12,8 +12,7 @@ QT_BEGIN_NAMESPACE // Implements the Toggle control pattern provider. Used for checkboxes. -class QWindowsUiaToggleProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IToggleProvider> +class QWindowsUiaToggleProvider : public QWindowsUiaBaseProvider, public QComObject<IToggleProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaToggleProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp index 9287e89084..78ab3e890e 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp @@ -176,6 +176,9 @@ long roleToControlTypeId(QAccessible::Role role) {QAccessible::PageTabList, UIA_TabControlTypeId}, {QAccessible::Clock, UIA_CustomControlTypeId}, {QAccessible::Splitter, UIA_CustomControlTypeId}, + {QAccessible::Paragraph, UIA_TextControlTypeId}, + {QAccessible::WebDocument, UIA_DocumentControlTypeId}, + {QAccessible::Heading, UIA_TextControlTypeId}, }; return mapping.value(role, UIA_CustomControlTypeId); diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h index 8fe8b1c6d7..bf90211cec 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h @@ -12,7 +12,7 @@ #include <QtGui/qaccessible.h> #include <QtGui/qwindow.h> #include <QtCore/qrect.h> -#include <QtGui/private/qwindowsuiawrapper_p.h> +#include "qwindowsuiautomation.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp new file mode 100644 index 0000000000..1593a07202 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtGui/qtguiglobal.h> +#if QT_CONFIG(accessibility) + +#include "qwindowsuiautomation.h" + +#if defined(__MINGW32__) || defined(__MINGW64__) + +template<typename T, typename... TArg> +struct winapi_func +{ + using func_t = T(WINAPI*)(TArg...); + const func_t func; + const T error_value; +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + winapi_func(const char *lib_name, const char *func_name, func_t func_proto, + T error_value = T(__HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND))) : + func(reinterpret_cast<func_t>(GetProcAddress(LoadLibraryA(lib_name), func_name))), + error_value(error_value) + { + std::ignore = func_proto; + } +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + T invoke(TArg... arg) + { + if (!func) + return error_value; + return func(arg...); + } +}; + +#define FN(fn) #fn,fn + +BOOL WINAPI UiaClientsAreListening() +{ + static auto func = winapi_func("uiautomationcore", FN(UiaClientsAreListening), BOOL(false)); + return func.invoke(); +} + +LRESULT WINAPI UiaReturnRawElementProvider( + HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el) +{ + static auto func = winapi_func("uiautomationcore", FN(UiaReturnRawElementProvider)); + return func.invoke(hwnd, wParam, lParam, el); +} + +HRESULT WINAPI UiaHostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider) +{ + static auto func = winapi_func("uiautomationcore", FN(UiaHostProviderFromHwnd)); + return func.invoke(hwnd, ppProvider); +} + +HRESULT WINAPI UiaRaiseAutomationPropertyChangedEvent( + IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue) +{ + static auto func = winapi_func("uiautomationcore", FN(UiaRaiseAutomationPropertyChangedEvent)); + return func.invoke(pProvider, id, oldValue, newValue); +} + +HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id) +{ + static auto func = winapi_func("uiautomationcore", FN(UiaRaiseAutomationEvent)); + return func.invoke(pProvider, id); +} + +#endif // defined(__MINGW32__) || defined(__MINGW64__) + +#endif // QT_CONFIG(accessibility) diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h new file mode 100644 index 0000000000..a192b9b0fb --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h @@ -0,0 +1,70 @@ +// Copyright (C) 2024 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 QWINDOWSUIAUTOMATION_H +#define QWINDOWSUIAUTOMATION_H + +#include <QtGui/qtguiglobal.h> +#if QT_CONFIG(accessibility) + +#include <uiautomation.h> + +#if defined(__MINGW32__) || defined(__MINGW64__) + +#define UIA_SelectionPattern2Id 10034 +#define UIA_IsReadOnlyAttributeId 40015 +#define UIA_StrikethroughStyleAttributeId 40026 +#define UIA_CaretPositionAttributeId 40038 + +enum CaretPosition { + CaretPosition_Unknown = 0, + CaretPosition_EndOfLine = 1, + CaretPosition_BeginningOfLine = 2 +}; + +enum TextDecorationLineStyle { + TextDecorationLineStyle_None = 0, + TextDecorationLineStyle_Single = 1, + TextDecorationLineStyle_WordsOnly = 2, + TextDecorationLineStyle_Double = 3, + TextDecorationLineStyle_Dot = 4, + TextDecorationLineStyle_Dash = 5, + TextDecorationLineStyle_DashDot = 6, + TextDecorationLineStyle_DashDotDot = 7, + TextDecorationLineStyle_Wavy = 8, + TextDecorationLineStyle_ThickSingle = 9, + TextDecorationLineStyle_DoubleWavy = 11, + TextDecorationLineStyle_ThickWavy = 12, + TextDecorationLineStyle_LongDash = 13, + TextDecorationLineStyle_ThickDash = 14, + TextDecorationLineStyle_ThickDashDot = 15, + TextDecorationLineStyle_ThickDashDotDot = 16, + TextDecorationLineStyle_ThickDot = 17, + TextDecorationLineStyle_ThickLongDash = 18, + TextDecorationLineStyle_Other = -1 +}; + +BOOL WINAPI UiaClientsAreListening(); + +#ifndef __ISelectionProvider2_INTERFACE_DEFINED__ +#define __ISelectionProvider2_INTERFACE_DEFINED__ +DEFINE_GUID(IID_ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7); +MIDL_INTERFACE("14f68475-ee1c-44f6-a869-d239381f0fe7") +ISelectionProvider2 : public ISelectionProvider +{ +public: + virtual HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0; + virtual HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0; + virtual HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0; + virtual HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *retVal) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7) +#endif +#endif // __ISelectionProvider2_INTERFACE_DEFINED__ + +#endif // defined(__MINGW32__) || defined(__MINGW64__) + +#endif // QT_CONFIG(accessibility) + +#endif // QWINDOWSUIAUTOMATION_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h index 2f52019d33..8c0a6b8ee7 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h @@ -13,8 +13,7 @@ QT_BEGIN_NAMESPACE // Implements the Value control pattern provider. // Supported for all controls that can return text(QAccessible::Value). -class QWindowsUiaValueProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IValueProvider> +class QWindowsUiaValueProvider : public QWindowsUiaBaseProvider, public QComObject<IValueProvider> { Q_DISABLE_COPY_MOVE(QWindowsUiaValueProvider) public: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h index e277b3f5c5..89a1655232 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h @@ -11,8 +11,7 @@ QT_BEGIN_NAMESPACE -class QWindowsUiaWindowProvider : public QWindowsUiaBaseProvider, - public QWindowsComBase<IWindowProvider> +class QWindowsUiaWindowProvider : public QWindowsUiaBaseProvider, public QComObject<IWindowProvider> { Q_DISABLE_COPY(QWindowsUiaWindowProvider) public: |