├── .gitattributes ├── designer.png ├── .editorconfig ├── qtbase └── src │ ├── corelib │ ├── thread │ │ ├── qmutex_win.cpp │ │ ├── qfutex_p.h │ │ ├── qmutex_p.h │ │ ├── qthread_win.cpp │ │ └── qmutex.cpp │ ├── kernel │ │ ├── qfunctions_win.cpp │ │ └── qeventdispatcher_win.cpp │ └── io │ │ └── qstandardpaths_win.cpp │ ├── plugins │ └── platforms │ │ └── windows │ │ ├── uiautomation │ │ ├── qwindowsuiawrapper_p.h │ │ ├── qwindowsuiawrapper.cpp │ │ └── qwindowsuiaaccessibility.cpp │ │ ├── qwin10helpers.cpp │ │ ├── qwindowscontext.h │ │ ├── vxkex.h │ │ ├── qwindowsintegration.cpp │ │ └── qwindowsdrag.cpp │ └── network │ └── kernel │ └── qdnslookup_win.cpp └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /designer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crystalidea/qt6windows7/HEAD/designer.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | indent_style = space 11 | indent_size = 4 -------------------------------------------------------------------------------- /qtbase/src/corelib/thread/qmutex_win.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qmutex.h" 5 | #include 6 | #include "qmutex_p.h" 7 | #include 8 | 9 | QT_BEGIN_NAMESPACE 10 | 11 | QMutexPrivate::QMutexPrivate() 12 | { 13 | event = CreateEvent(0, FALSE, FALSE, 0); 14 | 15 | if (!event) 16 | qWarning("QMutexPrivate::QMutexPrivate: Cannot create event"); 17 | } 18 | 19 | QMutexPrivate::~QMutexPrivate() 20 | { CloseHandle(event); } 21 | 22 | bool QMutexPrivate::wait(QDeadlineTimer timeout) 23 | { 24 | return (WaitForSingleObjectEx(event, timeout.isForever() ? INFINITE : timeout.remainingTime(), FALSE) == WAIT_OBJECT_0); 25 | } 26 | 27 | void QMutexPrivate::wakeUp() noexcept 28 | { SetEvent(event); } 29 | 30 | QT_END_NAMESPACE -------------------------------------------------------------------------------- /qtbase/src/corelib/thread/qfutex_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 Intel Corporation. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QFUTEX_P_H 5 | #define QFUTEX_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | 21 | QT_BEGIN_NAMESPACE 22 | 23 | namespace QtDummyFutex { 24 | constexpr inline bool futexAvailable() { return false; } 25 | template 26 | inline bool futexWait(Atomic &, typename Atomic::Type, QDeadlineTimer = {}) 27 | { Q_UNREACHABLE_RETURN(false); } 28 | template inline void futexWakeOne(Atomic &) 29 | { Q_UNREACHABLE(); } 30 | template inline void futexWakeAll(Atomic &) 31 | { Q_UNREACHABLE(); } 32 | } 33 | 34 | QT_END_NAMESPACE 35 | 36 | #if defined(Q_OS_DARWIN) 37 | # include "qfutex_mac_p.h" 38 | #elif defined(Q_OS_FREEBSD) 39 | # include "qfutex_freebsd_p.h" 40 | #elif defined(Q_OS_LINUX) && !defined(QT_LINUXBASE) 41 | // use Linux mutexes everywhere except for LSB builds 42 | # include "qfutex_linux_p.h" 43 | //#elif defined(Q_OS_WIN) 44 | //# include "qfutex_win_p.h" 45 | #else 46 | QT_BEGIN_NAMESPACE 47 | namespace QtFutex = QtDummyFutex; 48 | QT_END_NAMESPACE 49 | #endif 50 | 51 | #endif // QFUTEX_P_H 52 | -------------------------------------------------------------------------------- /qtbase/src/corelib/kernel/qfunctions_win.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qfunctions_win_p.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #if __has_include() 12 | # include 13 | # define HAS_APPMODEL 14 | #endif 15 | 16 | QT_BEGIN_NAMESPACE 17 | 18 | QComHelper::QComHelper(COINIT concurrencyModel) 19 | { 20 | // Avoid overhead of initializing and using obsolete technology 21 | concurrencyModel = COINIT(concurrencyModel | COINIT_DISABLE_OLE1DDE); 22 | 23 | m_initResult = CoInitializeEx(nullptr, concurrencyModel); 24 | 25 | if (FAILED(m_initResult)) 26 | qErrnoWarning(m_initResult, "Failed to initialize COM library"); 27 | } 28 | 29 | QComHelper::~QComHelper() 30 | { 31 | Q_ASSERT(m_threadId == GetCurrentThreadId()); 32 | if (SUCCEEDED(m_initResult)) 33 | CoUninitialize(); 34 | } 35 | 36 | /*! 37 | \internal 38 | Checks if the application has a \e{package identity} 39 | 40 | Having a \e{package identity} is required to use many modern 41 | Windows APIs. 42 | 43 | https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-packaged-apps 44 | */ 45 | bool qt_win_hasPackageIdentity() 46 | { 47 | typedef BOOL (WINAPI *GetCurrentPackageFullNameFunc) (UINT32 *, PWSTR); 48 | static GetCurrentPackageFullNameFunc myGetCurrentPackageFullName = 49 | (GetCurrentPackageFullNameFunc)::GetProcAddress(::GetModuleHandle(L"kernel32"), "GetCurrentPackageFullName"); 50 | 51 | if (myGetCurrentPackageFullName) 52 | { 53 | #if defined(HAS_APPMODEL) 54 | 55 | static const bool hasPackageIdentity = []() { 56 | UINT32 length = 0; 57 | switch (const auto result = myGetCurrentPackageFullName(&length, nullptr)) { 58 | case ERROR_INSUFFICIENT_BUFFER: 59 | return true; 60 | case APPMODEL_ERROR_NO_PACKAGE: 61 | return false; 62 | default: 63 | qWarning("Failed to resolve package identity (error code %ld)", result); 64 | return false; 65 | } 66 | }(); 67 | return hasPackageIdentity; 68 | #else 69 | return false; 70 | #endif 71 | } 72 | 73 | return false; 74 | } 75 | 76 | QT_END_NAMESPACE 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository provides a backport of the Qt 6 qtbase module, tailored for compatibility with Windows 7, 8 and 8.1. It contains patched source files from the qtbase module, along with some additional required files. To apply the backport, simply copy the contents of the src folder into your qtbase/src directory, replacing the existing files. 2 | 3 | The most recent supported version is **6.8.3** however many older versions are supported as well (see **Older versions** section). 4 | 5 | This approach builds upon the methodology discussed in this forum [thread](https://forum.qt.io/topic/133002/qt-creator-6-0-1-and-qt-6-2-2-running-on-windows-7/60) but offers significant enhancements, including important fallbacks to the default Qt 6 behavior when running on newer versions of Windows. 6 | 7 | You can compile it yourself using your preferred compiler and build options or can use our [compile_win.pl](https://github.com/crystalidea/qt-build-tools/tree/master/6.8.1) build script, which utilizes Visual C++ 2022 and includes OpenSSL 3.0.13 statically linked. Alternatively, you can download our [prebuild Qt dlls](https://github.com/crystalidea/qt6windows7/releases), which also include the Qt Designer binary for demonstration purposes. 8 | 9 | **Qt 6.8.3 designer running on Windows 7**: 10 | 11 | ![Qt Designer](designer.png) 12 | 13 | **Other modules**: 14 | 15 | Many of other Qt 6 modules are known to work fine on Windows 7 without modifications when compiled with patched qtbase. Verified modules: 16 | 17 | - qt5compat 18 | - qtimageformats 19 | - qttools 20 | - ... please let me know which work and which don't ! 21 | 22 | ### Known issues: 23 | 24 | - QRhi using DirectX 11/12 is not ported 25 | 26 | ### Older versions: 27 | 28 | - [Qt 6.8.2](https://github.com/crystalidea/qt6windows7/releases/tag/v6.8.2) 29 | - [Qt 6.8.1](https://github.com/crystalidea/qt6windows7/releases/tag/v6.8.1) 30 | - [Qt 6.8.0](https://github.com/crystalidea/qt6windows7/releases/tag/v6.8.0) 31 | - [Qt 6.7.2](https://github.com/crystalidea/qt6windows7/releases/tag/v6.7.2) 32 | - [Qt 6.6.3](https://github.com/crystalidea/qt6windows7/releases/tag/v6.6.3) 33 | - [Qt 6.6.2](https://github.com/crystalidea/qt6windows7/releases/tag/v6.6.2) 34 | - [Qt 6.6.1](https://github.com/crystalidea/qt6windows7/releases/tag/v6.6.1) 35 | - [Qt 6.6.0](https://github.com/crystalidea/qt6windows7/releases/tag/v6.6.0) 36 | - [Qt 6.5.3](https://github.com/crystalidea/qt6windows7/releases/tag/6.5.3-win7) 37 | - [Qt 6.5.1](https://github.com/crystalidea/qt6windows7/releases/tag/6.5.1-win7) 38 | 39 | ### License 40 | 41 | The repository shares Qt Community Edition terms which imply [Open-Source terms and conditions (GPL and LGPL)](https://www.qt.io/licensing/open-source-lgpl-obligations?hsLang=en). 42 | -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QWINDOWSUIAWRAPPER_H 5 | #define QWINDOWSUIAWRAPPER_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists for the convenience 12 | // of other Qt classes. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | 20 | QT_REQUIRE_CONFIG(accessibility); 21 | 22 | QT_BEGIN_NAMESPACE 23 | 24 | class QWindowsUiaWrapper 25 | { 26 | QWindowsUiaWrapper(); 27 | virtual ~QWindowsUiaWrapper(); 28 | public: 29 | static QWindowsUiaWrapper *instance(); 30 | BOOL ready(); 31 | BOOL clientsAreListening(); 32 | LRESULT returnRawElementProvider(HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el); 33 | HRESULT hostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider); 34 | HRESULT raiseAutomationPropertyChangedEvent(IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue); 35 | HRESULT raiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id); 36 | HRESULT raiseNotificationEvent(IRawElementProviderSimple *pProvider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId); 37 | 38 | private: 39 | typedef LRESULT (WINAPI *PtrUiaReturnRawElementProvider)(HWND, WPARAM, LPARAM, IRawElementProviderSimple *); 40 | typedef HRESULT (WINAPI *PtrUiaHostProviderFromHwnd)(HWND, IRawElementProviderSimple **); 41 | typedef HRESULT (WINAPI *PtrUiaRaiseAutomationPropertyChangedEvent)(IRawElementProviderSimple *, PROPERTYID, VARIANT, VARIANT); 42 | typedef HRESULT (WINAPI *PtrUiaRaiseAutomationEvent)(IRawElementProviderSimple *, EVENTID); 43 | typedef HRESULT (WINAPI *PtrUiaRaiseNotificationEvent)(IRawElementProviderSimple *, NotificationKind, NotificationProcessing, BSTR, BSTR); 44 | 45 | typedef BOOL (WINAPI *PtrUiaClientsAreListening)(); 46 | PtrUiaReturnRawElementProvider m_pUiaReturnRawElementProvider = nullptr; 47 | PtrUiaHostProviderFromHwnd m_pUiaHostProviderFromHwnd = nullptr; 48 | PtrUiaRaiseAutomationPropertyChangedEvent m_pUiaRaiseAutomationPropertyChangedEvent = nullptr; 49 | PtrUiaRaiseAutomationEvent m_pUiaRaiseAutomationEvent = nullptr; 50 | PtrUiaRaiseNotificationEvent m_pUiaRaiseNotificationEvent = nullptr; 51 | PtrUiaClientsAreListening m_pUiaClientsAreListening = nullptr; 52 | }; 53 | 54 | QT_END_NAMESPACE 55 | 56 | #endif //QWINDOWSUIAWRAPPER_H 57 | 58 | -------------------------------------------------------------------------------- /qtbase/src/corelib/thread/qmutex_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // Copyright (C) 2016 Intel Corporation. 3 | // Copyright (C) 2012 Olivier Goffart 4 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 5 | 6 | #ifndef QMUTEX_P_H 7 | #define QMUTEX_P_H 8 | 9 | // 10 | // W A R N I N G 11 | // ------------- 12 | // 13 | // This file is not part of the Qt API. It exists for the convenience of 14 | // qmutex.cpp and qmutex_unix.cpp. This header file may change from version to 15 | // version without notice, or even be removed. 16 | // 17 | // We mean it. 18 | // 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "qplatformdefs.h" // _POSIX_VERSION 27 | 28 | #if defined(Q_OS_DARWIN) 29 | # include 30 | #elif defined(Q_OS_UNIX) 31 | # include 32 | #endif 33 | 34 | struct timespec; 35 | 36 | QT_BEGIN_NAMESPACE 37 | 38 | // We manipulate the pointer to this class in inline, atomic code, 39 | // so syncqt mustn't mark them as private, so ELFVERSION:ignore-next 40 | class QMutexPrivate 41 | { 42 | public: 43 | ~QMutexPrivate(); 44 | QMutexPrivate(); 45 | 46 | bool wait(QDeadlineTimer timeout = QDeadlineTimer::Forever); 47 | void wakeUp() noexcept; 48 | 49 | // Control the lifetime of the privates 50 | QAtomicInt refCount; 51 | int id; 52 | 53 | bool ref() 54 | { 55 | Q_ASSERT(refCount.loadRelaxed() >= 0); 56 | int c; 57 | do { 58 | c = refCount.loadRelaxed(); 59 | if (c == 0) 60 | return false; 61 | } while (!refCount.testAndSetRelaxed(c, c + 1)); 62 | Q_ASSERT(refCount.loadRelaxed() >= 0); 63 | return true; 64 | } 65 | void deref() 66 | { 67 | Q_ASSERT(refCount.loadRelaxed() >= 0); 68 | if (!refCount.deref()) 69 | release(); 70 | Q_ASSERT(refCount.loadRelaxed() >= 0); 71 | } 72 | void release(); 73 | static QMutexPrivate *allocate(); 74 | 75 | QAtomicInt waiters; // Number of threads waiting on this mutex. (may be offset by -BigNumber) 76 | QAtomicInt possiblyUnlocked; /* Boolean indicating that a timed wait timed out. 77 | When it is true, a reference is held. 78 | It is there to avoid a race that happens if unlock happens right 79 | when the mutex is unlocked. 80 | */ 81 | enum { BigNumber = 0x100000 }; //Must be bigger than the possible number of waiters (number of threads) 82 | void derefWaiters(int value) noexcept; 83 | 84 | //platform specific stuff 85 | #if defined(Q_OS_DARWIN) 86 | semaphore_t mach_semaphore; 87 | #elif defined(Q_OS_UNIX) 88 | sem_t semaphore; 89 | #elif defined(Q_OS_WIN) 90 | Qt::HANDLE event; 91 | #endif 92 | }; 93 | 94 | QT_END_NAMESPACE 95 | 96 | #endif // QMUTEX_P_H 97 | -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiawrapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include 5 | 6 | #include "qwindowsuiawrapper_p.h" 7 | #include 8 | 9 | QT_BEGIN_NAMESPACE 10 | 11 | // private constructor 12 | QWindowsUiaWrapper::QWindowsUiaWrapper() 13 | { 14 | QSystemLibrary uiaLib(QStringLiteral("UIAutomationCore")); 15 | if (uiaLib.load()) { 16 | m_pUiaReturnRawElementProvider = reinterpret_cast(uiaLib.resolve("UiaReturnRawElementProvider")); 17 | m_pUiaHostProviderFromHwnd = reinterpret_cast(uiaLib.resolve("UiaHostProviderFromHwnd")); 18 | m_pUiaRaiseAutomationPropertyChangedEvent = reinterpret_cast(uiaLib.resolve("UiaRaiseAutomationPropertyChangedEvent")); 19 | m_pUiaRaiseAutomationEvent = reinterpret_cast(uiaLib.resolve("UiaRaiseAutomationEvent")); 20 | m_pUiaRaiseNotificationEvent = reinterpret_cast(uiaLib.resolve("UiaRaiseNotificationEvent")); 21 | m_pUiaClientsAreListening = reinterpret_cast(uiaLib.resolve("UiaClientsAreListening")); 22 | } 23 | } 24 | 25 | QWindowsUiaWrapper::~QWindowsUiaWrapper() 26 | { 27 | } 28 | 29 | // shared instance 30 | QWindowsUiaWrapper *QWindowsUiaWrapper::instance() 31 | { 32 | static QWindowsUiaWrapper wrapper; 33 | return &wrapper; 34 | } 35 | 36 | // True if all symbols resolved. 37 | BOOL QWindowsUiaWrapper::ready() 38 | { 39 | return m_pUiaReturnRawElementProvider 40 | && m_pUiaHostProviderFromHwnd 41 | && m_pUiaRaiseAutomationPropertyChangedEvent 42 | && m_pUiaRaiseAutomationEvent 43 | && m_pUiaClientsAreListening; 44 | } 45 | 46 | BOOL QWindowsUiaWrapper::clientsAreListening() 47 | { 48 | if (!m_pUiaClientsAreListening) 49 | return FALSE; 50 | return m_pUiaClientsAreListening(); 51 | } 52 | 53 | LRESULT QWindowsUiaWrapper::returnRawElementProvider(HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el) 54 | { 55 | if (!m_pUiaReturnRawElementProvider) 56 | return static_cast(NULL); 57 | return m_pUiaReturnRawElementProvider(hwnd, wParam, lParam, el); 58 | } 59 | 60 | HRESULT QWindowsUiaWrapper::hostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider) 61 | { 62 | if (!m_pUiaHostProviderFromHwnd) 63 | return UIA_E_NOTSUPPORTED; 64 | return m_pUiaHostProviderFromHwnd(hwnd, ppProvider); 65 | } 66 | 67 | HRESULT QWindowsUiaWrapper::raiseAutomationPropertyChangedEvent(IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue) 68 | { 69 | if (!m_pUiaRaiseAutomationPropertyChangedEvent) 70 | return UIA_E_NOTSUPPORTED; 71 | return m_pUiaRaiseAutomationPropertyChangedEvent(pProvider, id, oldValue, newValue); 72 | } 73 | 74 | HRESULT QWindowsUiaWrapper::raiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id) 75 | { 76 | if (!m_pUiaRaiseAutomationEvent) 77 | return UIA_E_NOTSUPPORTED; 78 | return m_pUiaRaiseAutomationEvent(pProvider, id); 79 | } 80 | 81 | HRESULT QWindowsUiaWrapper::raiseNotificationEvent(IRawElementProviderSimple *pProvider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId) 82 | { 83 | if (!m_pUiaRaiseNotificationEvent) 84 | return UIA_E_NOTSUPPORTED; 85 | return m_pUiaRaiseNotificationEvent(pProvider, notificationKind, notificationProcessing, displayString, activityId); 86 | } 87 | 88 | QT_END_NAMESPACE 89 | 90 | -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/qwin10helpers.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qwin10helpers.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if defined(Q_CC_MINGW) || defined(Q_CC_CLANG) 11 | # define HAS_UI_VIEW_SETTINGS_INTEROP 12 | // Present from MSVC2015 + SDK 10 onwards 13 | #elif (!defined(Q_CC_MSVC) || _MSC_VER >= 1900) && WINVER >= 0x0A00 14 | # define HAS_UI_VIEW_SETTINGS_INTEROP 15 | # define HAS_UI_VIEW_SETTINGS 16 | #endif 17 | 18 | #include 19 | 20 | #ifdef HAS_UI_VIEW_SETTINGS 21 | # include 22 | #endif 23 | 24 | #ifdef HAS_UI_VIEW_SETTINGS_INTEROP 25 | # include 26 | #endif 27 | 28 | #ifndef HAS_UI_VIEW_SETTINGS_INTEROP 29 | MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6") 30 | IUIViewSettingsInterop : public IInspectable 31 | { 32 | public: 33 | virtual HRESULT STDMETHODCALLTYPE GetForWindow( 34 | __RPC__in HWND hwnd, 35 | __RPC__in REFIID riid, 36 | __RPC__deref_out_opt void **ppv) = 0; 37 | }; 38 | #endif // !HAS_UI_VIEW_SETTINGS_INTEROP 39 | 40 | #ifndef HAS_UI_VIEW_SETTINGS 41 | namespace ABI { 42 | namespace Windows { 43 | namespace UI { 44 | namespace ViewManagement { 45 | 46 | enum UserInteractionMode { Mouse, Touch }; 47 | 48 | MIDL_INTERFACE("C63657F6-8850-470D-88F8-455E16EA2C26") 49 | IUIViewSettings : public IInspectable 50 | { 51 | public: 52 | virtual HRESULT STDMETHODCALLTYPE get_UserInteractionMode(UserInteractionMode *value) = 0; 53 | }; 54 | 55 | } // namespace ViewManagement 56 | } // namespace UI 57 | } // namespace Windows 58 | } // namespace ABI 59 | #endif // HAS_UI_VIEW_SETTINGS 60 | 61 | // based on SDL approach 62 | // see SDL_windows_gaming_input.c, SDL_windows.c 63 | 64 | void * WIN_LoadComBaseFunction(const char *name) 65 | { 66 | static bool s_bLoaded = false; 67 | static HMODULE s_hComBase = NULL; 68 | 69 | if (!s_bLoaded) { 70 | s_hComBase = ::LoadLibraryEx(L"combase.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); 71 | s_bLoaded = true; 72 | } 73 | if (s_hComBase) { 74 | return (void *) ::GetProcAddress(s_hComBase, name); 75 | } else { 76 | return NULL; 77 | } 78 | } 79 | 80 | QT_BEGIN_NAMESPACE 81 | 82 | // Return tablet mode, note: Does not work for GetDesktopWindow(). 83 | bool qt_windowsIsTabletMode(HWND hwnd) 84 | { 85 | typedef HRESULT (WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING* string); 86 | typedef HRESULT (WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void** factory); 87 | 88 | WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference"); 89 | RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory"); 90 | 91 | if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) 92 | { 93 | bool result = false; 94 | 95 | const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings"; 96 | HSTRING_HEADER uiViewSettingsIdRefHeader; 97 | HSTRING uiViewSettingsIdHs = nullptr; 98 | const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1); 99 | if (FAILED(WindowsCreateStringReferenceFunc(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs))) 100 | return false; 101 | 102 | IUIViewSettingsInterop *uiViewSettingsInterop = nullptr; 103 | // __uuidof(IUIViewSettingsInterop); 104 | const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}}; 105 | 106 | HRESULT hr = RoGetActivationFactoryFunc(uiViewSettingsIdHs, uiViewSettingsInteropRefId, 107 | reinterpret_cast(&uiViewSettingsInterop)); 108 | if (FAILED(hr)) 109 | return false; 110 | 111 | // __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings); 112 | const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}}; 113 | ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr; 114 | hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId, 115 | reinterpret_cast(&viewSettings)); 116 | if (SUCCEEDED(hr)) { 117 | ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode; 118 | hr = viewSettings->get_UserInteractionMode(¤tMode); 119 | if (SUCCEEDED(hr)) 120 | result = currentMode == 1; // Touch, 1 121 | viewSettings->Release(); 122 | } 123 | uiViewSettingsInterop->Release(); 124 | return result; 125 | } 126 | 127 | return false; 128 | } 129 | 130 | QT_END_NAMESPACE -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include 5 | #if QT_CONFIG(accessibility) 6 | 7 | #include "qwindowsuiaaccessibility.h" 8 | #include "qwindowsuiautomation.h" 9 | #include "qwindowsuiamainprovider.h" 10 | #include "qwindowsuiautils.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "qwindowsuiawrapper_p.h" 19 | #include "qwindowsuiawrapper.cpp" 20 | 21 | #include 22 | 23 | QT_BEGIN_NAMESPACE 24 | 25 | using namespace QWindowsUiAutomation; 26 | using namespace Qt::Literals::StringLiterals; 27 | 28 | bool QWindowsUiaAccessibility::m_accessibleActive = false; 29 | 30 | QWindowsUiaAccessibility::QWindowsUiaAccessibility() 31 | { 32 | } 33 | 34 | QWindowsUiaAccessibility::~QWindowsUiaAccessibility() 35 | { 36 | } 37 | 38 | // Handles UI Automation window messages. 39 | bool QWindowsUiaAccessibility::handleWmGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult) 40 | { 41 | // Start handling accessibility internally 42 | QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true); 43 | m_accessibleActive = true; 44 | 45 | // Ignoring all requests while starting up / shutting down 46 | if (QCoreApplication::startingUp() || QCoreApplication::closingDown()) 47 | return false; 48 | 49 | if (QWindow *window = QWindowsContext::instance()->findWindow(hwnd)) { 50 | if (QAccessibleInterface *accessible = window->accessibleRoot()) { 51 | auto provider = QWindowsUiaMainProvider::providerForAccessible(accessible); 52 | *lResult = QWindowsUiaWrapper::instance()->returnRawElementProvider(hwnd, wParam, lParam, provider.Get()); 53 | return true; 54 | } 55 | } 56 | return false; 57 | } 58 | 59 | // Retrieve sound name by checking the icon property of a message box 60 | // should it be the event object. 61 | static QString alertSound(const QObject *object) 62 | { 63 | if (object->inherits("QMessageBox")) { 64 | enum MessageBoxIcon { // Keep in sync with QMessageBox::Icon 65 | Information = 1, 66 | Warning = 2, 67 | Critical = 3 68 | }; 69 | switch (object->property("icon").toInt()) { 70 | case Information: 71 | return QStringLiteral("SystemAsterisk"); 72 | case Warning: 73 | return QStringLiteral("SystemExclamation"); 74 | case Critical: 75 | return QStringLiteral("SystemHand"); 76 | } 77 | return QString(); 78 | } 79 | return QStringLiteral("SystemAsterisk"); 80 | } 81 | 82 | static QString soundFileName(const QString &soundName) 83 | { 84 | const QString key = "AppEvents\\Schemes\\Apps\\.Default\\"_L1 85 | + soundName + "\\.Current"_L1; 86 | return QWinRegistryKey(HKEY_CURRENT_USER, key).stringValue(L""); 87 | } 88 | 89 | static void playSystemSound(const QString &soundName) 90 | { 91 | if (!soundName.isEmpty() && !soundFileName(soundName).isEmpty()) { 92 | PlaySound(reinterpret_cast(soundName.utf16()), nullptr, 93 | SND_ALIAS | SND_ASYNC | SND_NODEFAULT | SND_NOWAIT); 94 | } 95 | } 96 | 97 | // Handles accessibility update notifications. 98 | void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) 99 | { 100 | if (!event) 101 | return; 102 | 103 | // Always handle system sound events 104 | switch (event->type()) { 105 | case QAccessible::PopupMenuStart: 106 | playSystemSound(QStringLiteral("MenuPopup")); 107 | break; 108 | case QAccessible::MenuCommand: 109 | playSystemSound(QStringLiteral("MenuCommand")); 110 | break; 111 | case QAccessible::Alert: 112 | playSystemSound(alertSound(event->object())); 113 | break; 114 | default: 115 | break; 116 | } 117 | 118 | // Ignore events sent before the first UI Automation 119 | // request or while QAccessible is being activated. 120 | if (!m_accessibleActive) 121 | return; 122 | 123 | QAccessibleInterface *accessible = event->accessibleInterface(); 124 | if (!isActive() || !accessible || !accessible->isValid()) 125 | return; 126 | 127 | // Ensures QWindowsUiaWrapper is properly initialized. 128 | if (!QWindowsUiaWrapper::instance()->ready()) 129 | return; 130 | 131 | // No need to do anything when nobody is listening. 132 | if (!QWindowsUiaWrapper::instance()->clientsAreListening()) 133 | return; 134 | 135 | switch (event->type()) { 136 | case QAccessible::Announcement: 137 | QWindowsUiaMainProvider::raiseNotification(static_cast(event)); 138 | break; 139 | case QAccessible::Focus: 140 | QWindowsUiaMainProvider::notifyFocusChange(event); 141 | break; 142 | case QAccessible::StateChanged: 143 | QWindowsUiaMainProvider::notifyStateChange(static_cast(event)); 144 | break; 145 | case QAccessible::ValueChanged: 146 | QWindowsUiaMainProvider::notifyValueChange(static_cast(event)); 147 | break; 148 | case QAccessible::NameChanged: 149 | QWindowsUiaMainProvider::notifyNameChange(event); 150 | break; 151 | case QAccessible::SelectionAdd: 152 | QWindowsUiaMainProvider::notifySelectionChange(event); 153 | break; 154 | case QAccessible::TextAttributeChanged: 155 | case QAccessible::TextColumnChanged: 156 | case QAccessible::TextInserted: 157 | case QAccessible::TextRemoved: 158 | case QAccessible::TextUpdated: 159 | case QAccessible::TextSelectionChanged: 160 | case QAccessible::TextCaretMoved: 161 | QWindowsUiaMainProvider::notifyTextChange(event); 162 | break; 163 | default: 164 | break; 165 | } 166 | } 167 | 168 | QT_END_NAMESPACE 169 | 170 | #endif // QT_CONFIG(accessibility) 171 | -------------------------------------------------------------------------------- /qtbase/src/network/kernel/qdnslookup_win.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Jeremy Lainé 2 | // Copyright (C) 2023 Intel Corporation. 3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 4 | 5 | #include 6 | #include "qdnslookup_p.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #ifndef DNS_ADDR_MAX_SOCKADDR_LENGTH 17 | // MinGW headers are missing almost all of this 18 | typedef struct Qt_DnsAddr { 19 | CHAR MaxSa[32]; 20 | DWORD DnsAddrUserDword[8]; 21 | } DNS_ADDR, *PDNS_ADDR; 22 | typedef struct Qt_DnsAddrArray { 23 | DWORD MaxCount; 24 | DWORD AddrCount; 25 | DWORD Tag; 26 | WORD Family; 27 | WORD WordReserved; 28 | DWORD Flags; 29 | DWORD MatchFlag; 30 | DWORD Reserved1; 31 | DWORD Reserved2; 32 | DNS_ADDR AddrArray[]; 33 | } DNS_ADDR_ARRAY, *PDNS_ADDR_ARRAY; 34 | # ifndef DNS_QUERY_RESULTS_VERSION1 35 | typedef struct Qt_DNS_QUERY_RESULT { 36 | ULONG Version; 37 | DNS_STATUS QueryStatus; 38 | ULONG64 QueryOptions; 39 | PDNS_RECORD pQueryRecords; 40 | PVOID Reserved; 41 | } DNS_QUERY_RESULT, *PDNS_QUERY_RESULT; 42 | typedef VOID WINAPI DNS_QUERY_COMPLETION_ROUTINE(PVOID pQueryContext,PDNS_QUERY_RESULT pQueryResults); 43 | typedef DNS_QUERY_COMPLETION_ROUTINE *PDNS_QUERY_COMPLETION_ROUTINE; 44 | # endif 45 | typedef struct Qt_DNS_QUERY_REQUEST { 46 | ULONG Version; 47 | PCWSTR QueryName; 48 | WORD QueryType; 49 | ULONG64 QueryOptions; 50 | PDNS_ADDR_ARRAY pDnsServerList; 51 | ULONG InterfaceIndex; 52 | PDNS_QUERY_COMPLETION_ROUTINE pQueryCompletionCallback; 53 | PVOID pQueryContext; 54 | } DNS_QUERY_REQUEST, *PDNS_QUERY_REQUEST; 55 | 56 | typedef void *PDNS_QUERY_CANCEL; // not really, but we don't need it 57 | extern "C" { 58 | DNS_STATUS WINAPI DnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest, 59 | PDNS_QUERY_RESULT pQueryResults, 60 | PDNS_QUERY_CANCEL pCancelHandle); 61 | } 62 | #endif 63 | 64 | QT_BEGIN_NAMESPACE 65 | 66 | void QDnsLookupRunnable::query(QDnsLookupReply *reply) 67 | { 68 | typedef BOOL (WINAPI *DnsQueryExFunc) (PDNS_QUERY_REQUEST, PDNS_QUERY_RESULT, PDNS_QUERY_CANCEL); 69 | static DnsQueryExFunc myDnsQueryEx = 70 | (DnsQueryExFunc)::GetProcAddress(::GetModuleHandle(L"Dnsapi"), "DnsQueryEx"); 71 | 72 | PDNS_RECORD ptrStart = nullptr; 73 | 74 | if (myDnsQueryEx) 75 | { 76 | // Perform DNS query. 77 | alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)]; 78 | DNS_QUERY_REQUEST request = {}; 79 | request.Version = 1; 80 | request.QueryName = reinterpret_cast(requestName.constData()); 81 | request.QueryType = requestType; 82 | request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN; 83 | 84 | if (!nameserver.isNull()) { 85 | memset(dnsAddresses, 0, sizeof(dnsAddresses)); 86 | request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY; 87 | auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1]; 88 | auto sa = new (addr[0].MaxSa) sockaddr; 89 | request.pDnsServerList->MaxCount = sizeof(dnsAddresses); 90 | request.pDnsServerList->AddrCount = 1; 91 | // ### setting port 53 seems to cause some systems to fail 92 | setSockaddr(sa, nameserver, port == DnsPort ? 0 : port); 93 | request.pDnsServerList->Family = sa->sa_family; 94 | } 95 | 96 | DNS_QUERY_RESULT results = {}; 97 | results.Version = 1; 98 | const DNS_STATUS status = myDnsQueryEx(&request, &results, nullptr); 99 | if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST) 100 | return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1); 101 | else if (status == ERROR_TIMEOUT) 102 | return reply->makeTimeoutError(); 103 | else if (status != ERROR_SUCCESS) 104 | return reply->makeResolverSystemError(status); 105 | 106 | ptrStart = results.pQueryRecords; 107 | } 108 | else 109 | { 110 | // Perform DNS query. 111 | PDNS_RECORD dns_records = 0; 112 | QByteArray requestNameUTF8 = requestName.toUtf8(); 113 | const QString requestNameUtf16 = QString::fromUtf8(requestNameUTF8.data(), requestNameUTF8.size()); 114 | IP4_ARRAY srvList; 115 | memset(&srvList, 0, sizeof(IP4_ARRAY)); 116 | if (!nameserver.isNull()) { 117 | if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) { 118 | // The below code is referenced from: http://support.microsoft.com/kb/831226 119 | srvList.AddrCount = 1; 120 | srvList.AddrArray[0] = htonl(nameserver.toIPv4Address()); 121 | } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) { 122 | // For supporting IPv6 nameserver addresses, we'll need to switch 123 | // from DnsQuey() to DnsQueryEx() as it supports passing an IPv6 124 | // address in the nameserver list 125 | qWarning("%s", "IPv6 addresses for nameservers are currently not supported"); 126 | reply->error = QDnsLookup::ResolverError; 127 | reply->errorString = tr("IPv6 addresses for nameservers are currently not supported"); 128 | return; 129 | } 130 | } 131 | const DNS_STATUS status = DnsQuery_W(reinterpret_cast(requestNameUtf16.utf16()), requestType, DNS_QUERY_STANDARD, &srvList, &dns_records, NULL); 132 | switch (status) { 133 | case ERROR_SUCCESS: 134 | break; 135 | case DNS_ERROR_RCODE_FORMAT_ERROR: 136 | reply->error = QDnsLookup::InvalidRequestError; 137 | reply->errorString = tr("Server could not process query"); 138 | return; 139 | case DNS_ERROR_RCODE_SERVER_FAILURE: 140 | case DNS_ERROR_RCODE_NOT_IMPLEMENTED: 141 | reply->error = QDnsLookup::ServerFailureError; 142 | reply->errorString = tr("Server failure"); 143 | return; 144 | case DNS_ERROR_RCODE_NAME_ERROR: 145 | reply->error = QDnsLookup::NotFoundError; 146 | reply->errorString = tr("Non existent domain"); 147 | return; 148 | case DNS_ERROR_RCODE_REFUSED: 149 | reply->error = QDnsLookup::ServerRefusedError; 150 | reply->errorString = tr("Server refused to answer"); 151 | return; 152 | default: 153 | reply->error = QDnsLookup::InvalidReplyError; 154 | reply->errorString = QSystemError(status, QSystemError::NativeError).toString(); 155 | return; 156 | } 157 | 158 | ptrStart = dns_records; 159 | } 160 | 161 | if (!ptrStart) 162 | return; 163 | 164 | QStringView lastEncodedName; 165 | QString cachedDecodedName; 166 | auto extractAndCacheHost = [&](QStringView name) -> const QString & { 167 | lastEncodedName = name; 168 | cachedDecodedName = decodeLabel(name); 169 | return cachedDecodedName; 170 | }; 171 | auto extractMaybeCachedHost = [&](QStringView name) -> const QString & { 172 | return lastEncodedName == name ? cachedDecodedName : extractAndCacheHost(name); 173 | }; 174 | 175 | // Extract results. 176 | for (PDNS_RECORD ptr = ptrStart; ptr != NULL; ptr = ptr->pNext) { 177 | // warning: always assign name to the record before calling extractXxxHost() again 178 | const QString &name = extractMaybeCachedHost(ptr->pName); 179 | if (ptr->wType == QDnsLookup::A) { 180 | QDnsHostAddressRecord record; 181 | record.d->name = name; 182 | record.d->timeToLive = ptr->dwTtl; 183 | record.d->value = QHostAddress(ntohl(ptr->Data.A.IpAddress)); 184 | reply->hostAddressRecords.append(record); 185 | } else if (ptr->wType == QDnsLookup::AAAA) { 186 | Q_IPV6ADDR addr; 187 | memcpy(&addr, &ptr->Data.AAAA.Ip6Address, sizeof(Q_IPV6ADDR)); 188 | 189 | QDnsHostAddressRecord record; 190 | record.d->name = name; 191 | record.d->timeToLive = ptr->dwTtl; 192 | record.d->value = QHostAddress(addr); 193 | reply->hostAddressRecords.append(record); 194 | } else if (ptr->wType == QDnsLookup::CNAME) { 195 | QDnsDomainNameRecord record; 196 | record.d->name = name; 197 | record.d->timeToLive = ptr->dwTtl; 198 | record.d->value = extractAndCacheHost(ptr->Data.Cname.pNameHost); 199 | reply->canonicalNameRecords.append(record); 200 | } else if (ptr->wType == QDnsLookup::MX) { 201 | QDnsMailExchangeRecord record; 202 | record.d->name = name; 203 | record.d->exchange = decodeLabel(QStringView(ptr->Data.Mx.pNameExchange)); 204 | record.d->preference = ptr->Data.Mx.wPreference; 205 | record.d->timeToLive = ptr->dwTtl; 206 | reply->mailExchangeRecords.append(record); 207 | } else if (ptr->wType == QDnsLookup::NS) { 208 | QDnsDomainNameRecord record; 209 | record.d->name = name; 210 | record.d->timeToLive = ptr->dwTtl; 211 | record.d->value = decodeLabel(QStringView(ptr->Data.Ns.pNameHost)); 212 | reply->nameServerRecords.append(record); 213 | } else if (ptr->wType == QDnsLookup::PTR) { 214 | QDnsDomainNameRecord record; 215 | record.d->name = name; 216 | record.d->timeToLive = ptr->dwTtl; 217 | record.d->value = decodeLabel(QStringView(ptr->Data.Ptr.pNameHost)); 218 | reply->pointerRecords.append(record); 219 | } else if (ptr->wType == QDnsLookup::SRV) { 220 | QDnsServiceRecord record; 221 | record.d->name = name; 222 | record.d->target = decodeLabel(QStringView(ptr->Data.Srv.pNameTarget)); 223 | record.d->port = ptr->Data.Srv.wPort; 224 | record.d->priority = ptr->Data.Srv.wPriority; 225 | record.d->timeToLive = ptr->dwTtl; 226 | record.d->weight = ptr->Data.Srv.wWeight; 227 | reply->serviceRecords.append(record); 228 | } else if (ptr->wType == QDnsLookup::TXT) { 229 | QDnsTextRecord record; 230 | record.d->name = name; 231 | record.d->timeToLive = ptr->dwTtl; 232 | for (unsigned int i = 0; i < ptr->Data.Txt.dwStringCount; ++i) { 233 | record.d->values << QStringView(ptr->Data.Txt.pStringArray[i]).toLatin1(); 234 | } 235 | reply->textRecords.append(record); 236 | } 237 | } 238 | 239 | DnsRecordListFree(ptrStart, DnsFreeRecordList); 240 | } 241 | 242 | QT_END_NAMESPACE 243 | -------------------------------------------------------------------------------- /qtbase/src/corelib/io/qstandardpaths_win.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qstandardpaths.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifndef QT_BOOTSTRAPPED 10 | #include 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifndef QT_NO_STANDARDPATHS 20 | 21 | QT_BEGIN_NAMESPACE 22 | 23 | using namespace Qt::StringLiterals; 24 | 25 | static QString convertCharArray(const wchar_t *path) 26 | { 27 | return QDir::fromNativeSeparators(QString::fromWCharArray(path)); 28 | } 29 | 30 | static inline bool isGenericConfigLocation(QStandardPaths::StandardLocation type) 31 | { 32 | return type == QStandardPaths::GenericConfigLocation || type == QStandardPaths::GenericDataLocation; 33 | } 34 | 35 | static inline bool isConfigLocation(QStandardPaths::StandardLocation type) 36 | { 37 | return type == QStandardPaths::ConfigLocation || type == QStandardPaths::AppConfigLocation 38 | || type == QStandardPaths::AppDataLocation || type == QStandardPaths::AppLocalDataLocation 39 | || isGenericConfigLocation(type); 40 | } 41 | 42 | static void appendOrganizationAndApp(QString &path) // Courtesy qstandardpaths_unix.cpp 43 | { 44 | #ifndef QT_BOOTSTRAPPED 45 | const QString &org = QCoreApplication::organizationName(); 46 | if (!org.isEmpty()) 47 | path += u'/' + org; 48 | const QString &appName = QCoreApplication::applicationName(); 49 | if (!appName.isEmpty()) 50 | path += u'/' + appName; 51 | #else // !QT_BOOTSTRAPPED 52 | Q_UNUSED(path); 53 | #endif 54 | } 55 | 56 | static inline void appendTestMode(QString &path) 57 | { 58 | if (QStandardPaths::isTestModeEnabled()) 59 | path += "/qttest"_L1; 60 | } 61 | 62 | static bool isProcessLowIntegrity() 63 | { 64 | if (!IsWindows8OrGreater()) 65 | return false; 66 | 67 | // same as GetCurrentProcessToken() 68 | const auto process_token = HANDLE(quintptr(-4)); 69 | 70 | QVarLengthArray token_info_buf(256); 71 | auto* token_info = reinterpret_cast(token_info_buf.data()); 72 | DWORD token_info_length = token_info_buf.size(); 73 | if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length) 74 | && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 75 | // grow buffer and retry GetTokenInformation 76 | token_info_buf.resize(token_info_length); 77 | token_info = reinterpret_cast(token_info_buf.data()); 78 | if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)) 79 | return false; // assume "normal" process 80 | } 81 | else 82 | return false; 83 | 84 | // The GetSidSubAuthorityCount return-code is undefined on failure, so 85 | // there's no point in checking before dereferencing 86 | DWORD integrity_level = *GetSidSubAuthority(token_info->Label.Sid, *GetSidSubAuthorityCount(token_info->Label.Sid) - 1); 87 | return (integrity_level < SECURITY_MANDATORY_MEDIUM_RID); 88 | } 89 | 90 | // Map QStandardPaths::StandardLocation to KNOWNFOLDERID of SHGetKnownFolderPath() 91 | static GUID writableSpecialFolderId(QStandardPaths::StandardLocation type) 92 | { 93 | // folders for medium & high integrity processes 94 | static const GUID folderIds[] = { 95 | FOLDERID_Desktop, // DesktopLocation 96 | FOLDERID_Documents, // DocumentsLocation 97 | FOLDERID_Fonts, // FontsLocation 98 | FOLDERID_Programs, // ApplicationsLocation 99 | FOLDERID_Music, // MusicLocation 100 | FOLDERID_Videos, // MoviesLocation 101 | FOLDERID_Pictures, // PicturesLocation 102 | GUID(), GUID(), // TempLocation/HomeLocation 103 | FOLDERID_LocalAppData, // AppLocalDataLocation ("Local" path) 104 | GUID(), // CacheLocation 105 | FOLDERID_LocalAppData, // GenericDataLocation ("Local" path) 106 | GUID(), // RuntimeLocation 107 | FOLDERID_LocalAppData, // ConfigLocation ("Local" path) 108 | FOLDERID_Downloads, // DownloadLocation 109 | GUID(), // GenericCacheLocation 110 | FOLDERID_LocalAppData, // GenericConfigLocation ("Local" path) 111 | FOLDERID_RoamingAppData,// AppDataLocation ("Roaming" path) 112 | FOLDERID_LocalAppData, // AppConfigLocation ("Local" path) 113 | FOLDERID_Public, // PublicShareLocation 114 | FOLDERID_Templates, // TemplatesLocation 115 | GUID(), // StateLocation 116 | GUID(), // GenericStateLocation 117 | }; 118 | static_assert(sizeof(folderIds) / sizeof(folderIds[0]) == size_t(QStandardPaths::GenericStateLocation + 1)); 119 | 120 | // folders for low integrity processes 121 | static const GUID folderIds_li[] = { 122 | FOLDERID_Desktop, // DesktopLocation 123 | FOLDERID_Documents, // DocumentsLocation 124 | FOLDERID_Fonts, // FontsLocation 125 | FOLDERID_Programs, // ApplicationsLocation 126 | FOLDERID_Music, // MusicLocation 127 | FOLDERID_Videos, // MoviesLocation 128 | FOLDERID_Pictures, // PicturesLocation 129 | GUID(), GUID(), // TempLocation/HomeLocation 130 | FOLDERID_LocalAppDataLow,// AppLocalDataLocation ("Local" path) 131 | GUID(), // CacheLocation 132 | FOLDERID_LocalAppDataLow,// GenericDataLocation ("Local" path) 133 | GUID(), // RuntimeLocation 134 | FOLDERID_LocalAppDataLow,// ConfigLocation ("Local" path) 135 | FOLDERID_Downloads, // DownloadLocation 136 | GUID(), // GenericCacheLocation 137 | FOLDERID_LocalAppDataLow,// GenericConfigLocation ("Local" path) 138 | FOLDERID_RoamingAppData, // AppDataLocation ("Roaming" path) 139 | FOLDERID_LocalAppDataLow,// AppConfigLocation ("Local" path) 140 | FOLDERID_Public, // PublicShareLocation 141 | FOLDERID_Templates, // TemplatesLocation 142 | GUID(), // StateLocation 143 | GUID(), // GenericStateLocation 144 | }; 145 | static_assert(sizeof(folderIds_li) == sizeof(folderIds)); 146 | 147 | static bool low_integrity_process = isProcessLowIntegrity(); 148 | if (size_t(type) < sizeof(folderIds) / sizeof(folderIds[0])) 149 | return low_integrity_process ? folderIds_li[type] : folderIds[type]; 150 | return GUID(); 151 | } 152 | 153 | // Convenience for SHGetKnownFolderPath(). 154 | static QString sHGetKnownFolderPath(const GUID &clsid) 155 | { 156 | QString result; 157 | LPWSTR path; 158 | if (Q_LIKELY(SUCCEEDED(SHGetKnownFolderPath(clsid, KF_FLAG_DONT_VERIFY, 0, &path)))) { 159 | result = convertCharArray(path); 160 | CoTaskMemFree(path); 161 | } 162 | return result; 163 | } 164 | 165 | QString QStandardPaths::writableLocation(StandardLocation type) 166 | { 167 | QString result; 168 | switch (type) { 169 | case CacheLocation: 170 | // Although Microsoft has a Cache key it is a pointer to IE's cache, not a cache 171 | // location for everyone. Most applications seem to be using a 172 | // cache directory located in their AppData directory 173 | result = sHGetKnownFolderPath(writableSpecialFolderId(AppLocalDataLocation)); 174 | if (!result.isEmpty()) { 175 | appendTestMode(result); 176 | appendOrganizationAndApp(result); 177 | result += "/cache"_L1; 178 | } 179 | break; 180 | 181 | case GenericCacheLocation: 182 | result = sHGetKnownFolderPath(writableSpecialFolderId(GenericDataLocation)); 183 | if (!result.isEmpty()) { 184 | appendTestMode(result); 185 | result += "/cache"_L1; 186 | } 187 | break; 188 | 189 | case RuntimeLocation: 190 | case HomeLocation: 191 | result = QDir::homePath(); 192 | break; 193 | 194 | case TempLocation: 195 | result = QDir::tempPath(); 196 | break; 197 | 198 | case StateLocation: 199 | result = sHGetKnownFolderPath(writableSpecialFolderId(AppLocalDataLocation)); 200 | if (!result.isEmpty()) { 201 | appendTestMode(result); 202 | appendOrganizationAndApp(result); 203 | result += "/State"_L1; 204 | } 205 | break; 206 | 207 | case GenericStateLocation: 208 | result = sHGetKnownFolderPath(writableSpecialFolderId(GenericDataLocation)); 209 | if (!result.isEmpty()) { 210 | appendTestMode(result); 211 | result += "/State"_L1; 212 | } 213 | break; 214 | 215 | default: 216 | result = sHGetKnownFolderPath(writableSpecialFolderId(type)); 217 | if (!result.isEmpty() && isConfigLocation(type)) { 218 | appendTestMode(result); 219 | if (!isGenericConfigLocation(type)) 220 | appendOrganizationAndApp(result); 221 | } 222 | break; 223 | } 224 | return result; 225 | } 226 | 227 | #ifndef QT_BOOTSTRAPPED 228 | extern QString qAppFileName(); 229 | #endif 230 | 231 | QStringList QStandardPaths::standardLocations(StandardLocation type) 232 | { 233 | QStringList dirs; 234 | const QString localDir = writableLocation(type); 235 | if (!localDir.isEmpty()) 236 | dirs.append(localDir); 237 | 238 | // type-specific handling goes here 239 | if (isConfigLocation(type)) { 240 | QString programData = sHGetKnownFolderPath(FOLDERID_ProgramData); 241 | if (!programData.isEmpty()) { 242 | if (!isGenericConfigLocation(type)) 243 | appendOrganizationAndApp(programData); 244 | dirs.append(programData); 245 | } 246 | #ifndef QT_BOOTSTRAPPED 247 | // Note: QCoreApplication::applicationDirPath(), while static, requires 248 | // an application instance. But we might need to resolve the standard 249 | // locations earlier than that, so we fall back to qAppFileName(). 250 | QString applicationDirPath = qApp ? QCoreApplication::applicationDirPath() 251 | : QFileInfo(qAppFileName()).path(); 252 | dirs.append(applicationDirPath); 253 | const QString dataDir = applicationDirPath + "/data"_L1; 254 | dirs.append(dataDir); 255 | 256 | if (!isGenericConfigLocation(type)) { 257 | QString appDataDir = dataDir; 258 | appendOrganizationAndApp(appDataDir); 259 | if (appDataDir != dataDir) 260 | dirs.append(appDataDir); 261 | } 262 | #endif // !QT_BOOTSTRAPPED 263 | } // isConfigLocation() 264 | 265 | return dirs; 266 | } 267 | 268 | QT_END_NAMESPACE 269 | 270 | #endif // QT_NO_STANDARDPATHS 271 | -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/qwindowscontext.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QWINDOWSCONTEXT_H 5 | #define QWINDOWSCONTEXT_H 6 | 7 | #include "qtwindowsglobal.h" 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define STRICT_TYPED_ITEMIDS 15 | #include 16 | #include 17 | #include 18 | 19 | QT_BEGIN_NAMESPACE 20 | 21 | Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow) 22 | Q_DECLARE_LOGGING_CATEGORY(lcQpaEvents) 23 | Q_DECLARE_LOGGING_CATEGORY(lcQpaGl) 24 | Q_DECLARE_LOGGING_CATEGORY(lcQpaMime) 25 | Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods) 26 | Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs) 27 | Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus) 28 | Q_DECLARE_LOGGING_CATEGORY(lcQpaTablet) 29 | Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility) 30 | Q_DECLARE_LOGGING_CATEGORY(lcQpaUiAutomation) 31 | Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon) 32 | Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) 33 | 34 | class QWindow; 35 | class QPlatformScreen; 36 | class QPlatformWindow; 37 | class QPlatformKeyMapper; 38 | class QWindowsMenuBar; 39 | class QWindowsScreenManager; 40 | class QWindowsTabletSupport; 41 | class QWindowsWindow; 42 | class QWindowsMimeRegistry; 43 | struct QWindowCreationContext; 44 | struct QWindowsContextPrivate; 45 | class QPoint; 46 | class QKeyEvent; 47 | class QPointingDevice; 48 | 49 | struct QWindowsUser32DLL 50 | { 51 | inline void init(); 52 | inline bool supportsPointerApi(); 53 | 54 | typedef BOOL (WINAPI *EnableMouseInPointer)(BOOL); 55 | typedef BOOL (WINAPI *GetPointerType)(UINT32, PVOID); 56 | typedef BOOL (WINAPI *GetPointerInfo)(UINT32, PVOID); 57 | typedef BOOL (WINAPI *GetPointerDeviceRects)(HANDLE, RECT *, RECT *); 58 | typedef BOOL (WINAPI *GetPointerTouchInfo)(UINT32, PVOID); 59 | typedef BOOL (WINAPI *GetPointerFrameTouchInfo)(UINT32, UINT32 *, PVOID); 60 | typedef BOOL (WINAPI *GetPointerFrameTouchInfoHistory)(UINT32, UINT32 *, UINT32 *, PVOID); 61 | typedef BOOL (WINAPI *GetPointerPenInfo)(UINT32, PVOID); 62 | typedef BOOL (WINAPI *GetPointerPenInfoHistory)(UINT32, UINT32 *, PVOID); 63 | typedef BOOL (WINAPI *SkipPointerFrameMessages)(UINT32); 64 | typedef BOOL (WINAPI *SetProcessDPIAware)(); 65 | typedef BOOL (WINAPI *SetProcessDpiAwarenessContext)(HANDLE); 66 | typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND); 67 | typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); 68 | typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *); 69 | typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD); 70 | typedef BOOL (WINAPI *AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT); 71 | typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND); 72 | typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext)(HWND); 73 | typedef DPI_AWARENESS (WINAPI *GetAwarenessFromDpiAwarenessContext)(int); 74 | typedef BOOL (WINAPI *SystemParametersInfoForDpi)(UINT, UINT, PVOID, UINT, UINT); 75 | typedef int (WINAPI *GetDpiForWindow)(HWND); 76 | typedef BOOL (WINAPI *GetSystemMetricsForDpi)(INT, UINT); 77 | typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT); 78 | typedef DPI_AWARENESS_CONTEXT (WINAPI *GetThreadDpiAwarenessContext)(); 79 | typedef BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); 80 | 81 | // Windows pointer functions (Windows 8 or later). 82 | EnableMouseInPointer enableMouseInPointer = nullptr; 83 | GetPointerType getPointerType = nullptr; 84 | GetPointerInfo getPointerInfo = nullptr; 85 | GetPointerDeviceRects getPointerDeviceRects = nullptr; 86 | GetPointerTouchInfo getPointerTouchInfo = nullptr; 87 | GetPointerFrameTouchInfo getPointerFrameTouchInfo = nullptr; 88 | GetPointerFrameTouchInfoHistory getPointerFrameTouchInfoHistory = nullptr; 89 | GetPointerPenInfo getPointerPenInfo = nullptr; 90 | GetPointerPenInfoHistory getPointerPenInfoHistory = nullptr; 91 | SkipPointerFrameMessages skipPointerFrameMessages = nullptr; 92 | 93 | // Windows Vista onwards 94 | SetProcessDPIAware setProcessDPIAware = nullptr; 95 | 96 | // Windows 10 version 1607 onwards 97 | GetDpiForWindow getDpiForWindow = nullptr; 98 | GetThreadDpiAwarenessContext getThreadDpiAwarenessContext = nullptr; 99 | IsValidDpiAwarenessContext isValidDpiAwarenessContext = nullptr; 100 | 101 | // Windows 10 version 1703 onwards 102 | SetProcessDpiAwarenessContext setProcessDpiAwarenessContext = nullptr; 103 | AreDpiAwarenessContextsEqual areDpiAwarenessContextsEqual = nullptr; 104 | 105 | // Clipboard listeners are present on Windows Vista onwards 106 | // but missing in MinGW 4.9 stub libs. Can be removed in MinGW 5. 107 | AddClipboardFormatListener addClipboardFormatListener = nullptr; 108 | RemoveClipboardFormatListener removeClipboardFormatListener = nullptr; 109 | 110 | // Rotation API 111 | GetDisplayAutoRotationPreferences getDisplayAutoRotationPreferences = nullptr; 112 | SetDisplayAutoRotationPreferences setDisplayAutoRotationPreferences = nullptr; 113 | 114 | AdjustWindowRectExForDpi adjustWindowRectExForDpi = nullptr; 115 | EnableNonClientDpiScaling enableNonClientDpiScaling = nullptr; 116 | GetWindowDpiAwarenessContext getWindowDpiAwarenessContext = nullptr; 117 | GetAwarenessFromDpiAwarenessContext getAwarenessFromDpiAwarenessContext = nullptr; 118 | SystemParametersInfoForDpi systemParametersInfoForDpi = nullptr; 119 | GetSystemMetricsForDpi getSystemMetricsForDpi = nullptr; 120 | }; 121 | 122 | // Shell scaling library (Windows 8.1 onwards) 123 | struct QWindowsShcoreDLL 124 | { 125 | void init(); 126 | inline bool isValid() const { return getProcessDpiAwareness && setProcessDpiAwareness && getDpiForMonitor; } 127 | 128 | typedef HRESULT (WINAPI *GetProcessDpiAwareness)(HANDLE,PROCESS_DPI_AWARENESS *); 129 | typedef HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); 130 | typedef HRESULT (WINAPI *GetDpiForMonitor)(HMONITOR,int,UINT *,UINT *); 131 | 132 | GetProcessDpiAwareness getProcessDpiAwareness = nullptr; 133 | SetProcessDpiAwareness setProcessDpiAwareness = nullptr; 134 | GetDpiForMonitor getDpiForMonitor = nullptr; 135 | }; 136 | 137 | class QWindowsContext 138 | { 139 | Q_DISABLE_COPY_MOVE(QWindowsContext) 140 | public: 141 | using HandleBaseWindowHash = QHash; 142 | 143 | enum SystemInfoFlags 144 | { 145 | SI_RTL_Extensions = 0x1, 146 | SI_SupportsTouch = 0x2 147 | }; 148 | 149 | // Verbose flag set by environment variable QT_QPA_VERBOSE 150 | static int verbose; 151 | 152 | explicit QWindowsContext(); 153 | ~QWindowsContext(); 154 | 155 | bool initTouch(); 156 | bool initTouch(unsigned integrationOptions); // For calls from QWindowsIntegration::QWindowsIntegration() only. 157 | void registerTouchWindows(); 158 | bool initTablet(); 159 | bool disposeTablet(); 160 | 161 | bool initPowerNotificationHandler(); 162 | 163 | int defaultDPI() const; 164 | 165 | static QString classNamePrefix(); 166 | QString registerWindowClass(const QWindow *w); 167 | QString registerWindowClass(QString cname, WNDPROC proc, 168 | unsigned style = 0, HBRUSH brush = nullptr, 169 | bool icon = false); 170 | HWND createDummyWindow(const QString &classNameIn, 171 | const wchar_t *windowName, 172 | WNDPROC wndProc = nullptr, DWORD style = WS_OVERLAPPED); 173 | 174 | HDC displayContext() const; 175 | int screenDepth() const; 176 | 177 | static QWindowsContext *instance(); 178 | 179 | void addWindow(HWND, QWindowsWindow *w); 180 | void removeWindow(HWND); 181 | 182 | QWindowsWindow *findClosestPlatformWindow(HWND) const; 183 | QWindowsWindow *findPlatformWindow(HWND) const; 184 | QWindowsWindow *findPlatformWindow(const QWindowsMenuBar *mb) const; 185 | QWindow *findWindow(HWND) const; 186 | QWindowsWindow *findPlatformWindowAt(HWND parent, const QPoint &screenPoint, 187 | unsigned cwex_flags) const; 188 | 189 | static bool shouldHaveNonClientDpiScaling(const QWindow *window); 190 | 191 | QWindow *windowUnderMouse() const; 192 | void clearWindowUnderMouse(); 193 | 194 | inline bool windowsProc(HWND hwnd, UINT message, 195 | QtWindows::WindowsEventType et, 196 | WPARAM wParam, LPARAM lParam, LRESULT *result, 197 | QWindowsWindow **platformWindowPtr); 198 | 199 | QWindow *keyGrabber() const; 200 | void setKeyGrabber(QWindow *hwnd); 201 | 202 | QSharedPointer setWindowCreationContext(const QSharedPointer &ctx); 203 | QSharedPointer windowCreationContext() const; 204 | 205 | static void setTabletAbsoluteRange(int a); 206 | 207 | static bool setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness); 208 | static QtWindows::DpiAwareness processDpiAwareness(); 209 | static QtWindows::DpiAwareness windowDpiAwareness(HWND hwnd); 210 | 211 | void setDetectAltGrModifier(bool a); 212 | 213 | // Returns a combination of SystemInfoFlags 214 | unsigned systemInfo() const; 215 | 216 | bool useRTLExtensions() const; 217 | QPlatformKeyMapper *keyMapper() const; 218 | 219 | HandleBaseWindowHash &windows(); 220 | 221 | static bool isSessionLocked(); 222 | 223 | QWindowsMimeRegistry &mimeConverter() const; 224 | QWindowsScreenManager &screenManager(); 225 | QWindowsTabletSupport *tabletSupport() const; 226 | 227 | static QWindowsUser32DLL user32dll; 228 | static QWindowsShcoreDLL shcoredll; 229 | 230 | bool asyncExpose() const; 231 | void setAsyncExpose(bool value); 232 | 233 | static void forceNcCalcSize(HWND hwnd); 234 | 235 | static bool systemParametersInfo(unsigned action, unsigned param, void *out, unsigned dpi = 0); 236 | static bool systemParametersInfoForScreen(unsigned action, unsigned param, void *out, 237 | const QPlatformScreen *screen = nullptr); 238 | static bool systemParametersInfoForWindow(unsigned action, unsigned param, void *out, 239 | const QPlatformWindow *win = nullptr); 240 | static bool nonClientMetrics(NONCLIENTMETRICS *ncm, unsigned dpi = 0); 241 | static bool nonClientMetricsForScreen(NONCLIENTMETRICS *ncm, 242 | const QPlatformScreen *screen = nullptr); 243 | static bool nonClientMetricsForWindow(NONCLIENTMETRICS *ncm, 244 | const QPlatformWindow *win = nullptr); 245 | 246 | static DWORD readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue); 247 | 248 | static bool filterNativeEvent(MSG *msg, LRESULT *result); 249 | static bool filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result); 250 | 251 | private: 252 | void handleFocusEvent(QtWindows::WindowsEventType et, QWindowsWindow *w); 253 | #ifndef QT_NO_CONTEXTMENU 254 | bool handleContextMenuEvent(QWindow *window, const MSG &msg); 255 | #endif 256 | void handleExitSizeMove(QWindow *window); 257 | void unregisterWindowClasses(); 258 | 259 | QScopedPointer d; 260 | static QWindowsContext *m_instance; 261 | }; 262 | 263 | extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND, UINT, WPARAM, LPARAM); 264 | 265 | QT_END_NAMESPACE 266 | 267 | #endif // QWINDOWSCONTEXT_H 268 | -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/vxkex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define MDT_MAXIMUM_DPI 3 9 | 10 | namespace vxkex { 11 | 12 | static INT GetSystemMetricsForDpi( 13 | IN INT Index, 14 | IN UINT Dpi) 15 | { 16 | INT Value; 17 | 18 | Value = GetSystemMetrics(Index); 19 | 20 | switch (Index) { 21 | case SM_CXVSCROLL: 22 | case SM_CYHSCROLL: 23 | case SM_CYCAPTION: 24 | case SM_CYVTHUMB: 25 | case SM_CXHTHUMB: 26 | case SM_CXICON: 27 | case SM_CYICON: 28 | case SM_CXCURSOR: 29 | case SM_CYCURSOR: 30 | case SM_CYMENU: 31 | case SM_CYVSCROLL: 32 | case SM_CXHSCROLL: 33 | case SM_CXMIN: 34 | case SM_CXMINTRACK: 35 | case SM_CYMIN: 36 | case SM_CYMINTRACK: 37 | case SM_CXSIZE: 38 | case SM_CXFRAME: 39 | case SM_CYFRAME: 40 | case SM_CXICONSPACING: 41 | case SM_CYICONSPACING: 42 | case SM_CXSMICON: 43 | case SM_CYSMICON: 44 | case SM_CYSMCAPTION: 45 | case SM_CXSMSIZE: 46 | case SM_CYSMSIZE: 47 | case SM_CXMENUSIZE: 48 | case SM_CYMENUSIZE: 49 | case SM_CXMENUCHECK: 50 | case SM_CYMENUCHECK: 51 | // These are pixel values that have to be scaled according to DPI. 52 | Value *= Dpi; 53 | Value /= USER_DEFAULT_SCREEN_DPI; 54 | break; 55 | } 56 | 57 | return Value; 58 | } 59 | 60 | static BOOL SystemParametersInfoForDpi( 61 | IN UINT Action, 62 | IN UINT Parameter, 63 | IN OUT PVOID Data, 64 | IN UINT WinIni, 65 | IN UINT Dpi) 66 | { 67 | switch (Action) { 68 | case SPI_GETICONTITLELOGFONT: 69 | return SystemParametersInfo(Action, Parameter, Data, 0); 70 | case SPI_GETICONMETRICS: 71 | { 72 | BOOL Success; 73 | PICONMETRICS IconMetrics; 74 | 75 | Success = SystemParametersInfo(Action, Parameter, Data, 0); 76 | 77 | if (Success) { 78 | IconMetrics = (PICONMETRICS) Data; 79 | 80 | IconMetrics->iHorzSpacing *= Dpi; 81 | IconMetrics->iVertSpacing *= Dpi; 82 | IconMetrics->iHorzSpacing /= USER_DEFAULT_SCREEN_DPI; 83 | IconMetrics->iVertSpacing /= USER_DEFAULT_SCREEN_DPI; 84 | } 85 | 86 | return Success; 87 | } 88 | case SPI_GETNONCLIENTMETRICS: 89 | { 90 | BOOL Success; 91 | PNONCLIENTMETRICS NonClientMetrics; 92 | 93 | Success = SystemParametersInfo(Action, Parameter, Data, 0); 94 | 95 | if (Success) { 96 | NonClientMetrics = (PNONCLIENTMETRICS) Data; 97 | 98 | NonClientMetrics->iBorderWidth *= Dpi; 99 | NonClientMetrics->iScrollWidth *= Dpi; 100 | NonClientMetrics->iScrollHeight *= Dpi; 101 | NonClientMetrics->iCaptionWidth *= Dpi; 102 | NonClientMetrics->iCaptionHeight *= Dpi; 103 | NonClientMetrics->iSmCaptionWidth *= Dpi; 104 | NonClientMetrics->iSmCaptionHeight *= Dpi; 105 | NonClientMetrics->iMenuWidth *= Dpi; 106 | NonClientMetrics->iMenuHeight *= Dpi; 107 | NonClientMetrics->iPaddedBorderWidth *= Dpi; 108 | 109 | NonClientMetrics->iBorderWidth /= USER_DEFAULT_SCREEN_DPI; 110 | NonClientMetrics->iScrollWidth /= USER_DEFAULT_SCREEN_DPI; 111 | NonClientMetrics->iScrollHeight /= USER_DEFAULT_SCREEN_DPI; 112 | NonClientMetrics->iCaptionWidth /= USER_DEFAULT_SCREEN_DPI; 113 | NonClientMetrics->iCaptionHeight /= USER_DEFAULT_SCREEN_DPI; 114 | NonClientMetrics->iSmCaptionWidth /= USER_DEFAULT_SCREEN_DPI; 115 | NonClientMetrics->iSmCaptionHeight /= USER_DEFAULT_SCREEN_DPI; 116 | NonClientMetrics->iMenuWidth /= USER_DEFAULT_SCREEN_DPI; 117 | NonClientMetrics->iMenuHeight /= USER_DEFAULT_SCREEN_DPI; 118 | NonClientMetrics->iPaddedBorderWidth /= USER_DEFAULT_SCREEN_DPI; 119 | } 120 | 121 | return Success; 122 | } 123 | default: 124 | SetLastError(ERROR_INVALID_PARAMETER); 125 | return FALSE; 126 | } 127 | } 128 | 129 | static HRESULT GetScaleFactorForMonitor( 130 | IN HMONITOR Monitor, 131 | OUT DEVICE_SCALE_FACTOR *ScaleFactor) 132 | { 133 | HDC DeviceContext; 134 | ULONG LogPixelsX; 135 | 136 | DeviceContext = GetDC(NULL); 137 | if (!DeviceContext) { 138 | *ScaleFactor = SCALE_100_PERCENT; 139 | return S_OK; 140 | } 141 | 142 | LogPixelsX = GetDeviceCaps(DeviceContext, LOGPIXELSX); 143 | ReleaseDC(NULL, DeviceContext); 144 | 145 | *ScaleFactor = (DEVICE_SCALE_FACTOR) (9600 / LogPixelsX); 146 | return S_OK; 147 | } 148 | 149 | static HRESULT GetDpiForMonitor( 150 | IN HMONITOR Monitor, 151 | IN MONITOR_DPI_TYPE DpiType, 152 | OUT UINT * DpiX, 153 | OUT UINT * DpiY) 154 | { 155 | HDC DeviceContext; 156 | 157 | if (DpiType >= MDT_MAXIMUM_DPI) { 158 | return E_INVALIDARG; 159 | } 160 | 161 | if (!DpiX || !DpiY) { 162 | return E_INVALIDARG; 163 | } 164 | 165 | if (!IsProcessDPIAware()) { 166 | *DpiX = USER_DEFAULT_SCREEN_DPI; 167 | *DpiY = USER_DEFAULT_SCREEN_DPI; 168 | return S_OK; 169 | } 170 | 171 | DeviceContext = GetDC(NULL); 172 | if (!DeviceContext) { 173 | *DpiX = USER_DEFAULT_SCREEN_DPI; 174 | *DpiY = USER_DEFAULT_SCREEN_DPI; 175 | return S_OK; 176 | } 177 | 178 | *DpiX = GetDeviceCaps(DeviceContext, LOGPIXELSX); 179 | *DpiY = GetDeviceCaps(DeviceContext, LOGPIXELSY); 180 | 181 | if (DpiType == MDT_EFFECTIVE_DPI) { 182 | DEVICE_SCALE_FACTOR ScaleFactor; 183 | 184 | // We have to multiply the DPI values by the scaling factor. 185 | vxkex::GetScaleFactorForMonitor(Monitor, &ScaleFactor); 186 | 187 | *DpiX *= ScaleFactor; 188 | *DpiY *= ScaleFactor; 189 | *DpiX /= 100; 190 | *DpiY /= 100; 191 | } 192 | 193 | ReleaseDC(NULL, DeviceContext); 194 | return S_OK; 195 | } 196 | 197 | static UINT GetDpiForSystem( 198 | VOID) 199 | { 200 | HDC DeviceContext; 201 | ULONG LogPixelsX; 202 | 203 | if (!IsProcessDPIAware()) { 204 | return 96; 205 | } 206 | 207 | DeviceContext = GetDC(NULL); 208 | if (!DeviceContext) { 209 | return 96; 210 | } 211 | 212 | LogPixelsX = GetDeviceCaps(DeviceContext, LOGPIXELSX); 213 | ReleaseDC(NULL, DeviceContext); 214 | 215 | return LogPixelsX; 216 | } 217 | 218 | static UINT GetDpiForWindow( 219 | IN HWND Window) 220 | { 221 | if (!IsWindow(Window)) { 222 | return 0; 223 | } 224 | 225 | return vxkex::GetDpiForSystem(); 226 | } 227 | 228 | static BOOL AdjustWindowRectExForDpi( 229 | IN OUT LPRECT Rect, 230 | IN ULONG WindowStyle, 231 | IN BOOL HasMenu, 232 | IN ULONG WindowExStyle, 233 | IN ULONG Dpi) 234 | { 235 | // I'm not sure how to implement this function properly. 236 | // If it turns out to be important, I'll have to do some testing 237 | // on a Win10 VM. 238 | 239 | return AdjustWindowRectEx( 240 | Rect, 241 | WindowStyle, 242 | HasMenu, 243 | WindowExStyle); 244 | } 245 | 246 | static BOOL SetDisplayAutoRotationPreferences( 247 | IN ORIENTATION_PREFERENCE Orientation) 248 | { 249 | return TRUE; 250 | } 251 | 252 | static BOOL GetDisplayAutoRotationPreferences( 253 | OUT ORIENTATION_PREFERENCE * Orientation) 254 | { 255 | *Orientation = ORIENTATION_PREFERENCE_NONE; 256 | return TRUE; 257 | } 258 | 259 | // scaling.c 260 | 261 | static BOOL SetProcessDpiAwarenessContext( 262 | IN DPI_AWARENESS_CONTEXT DpiContext) 263 | { 264 | if ((ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE) { 265 | //NOTHING; 266 | } else if ((ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_SYSTEM_AWARE || 267 | (ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE || 268 | (ULONG_PTR)DpiContext == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) { 269 | SetProcessDPIAware(); 270 | } else { 271 | return FALSE; 272 | } 273 | 274 | return TRUE; 275 | } 276 | 277 | static BOOL AreDpiAwarenessContextsEqual( 278 | IN DPI_AWARENESS_CONTEXT Value1, 279 | IN DPI_AWARENESS_CONTEXT Value2) 280 | { 281 | return (Value1 == Value2); 282 | } 283 | 284 | static BOOL IsValidDpiAwarenessContext( 285 | IN DPI_AWARENESS_CONTEXT Value) 286 | { 287 | if ((ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE || 288 | (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED || 289 | (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_SYSTEM_AWARE || 290 | (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE || 291 | (ULONG_PTR)Value == (ULONG_PTR)DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) { 292 | return TRUE; 293 | } else { 294 | return FALSE; 295 | } 296 | } 297 | 298 | static BOOL EnableNonClientDpiScaling( 299 | IN HWND Window) 300 | { 301 | return TRUE; 302 | } 303 | 304 | static DPI_AWARENESS_CONTEXT GetThreadDpiAwarenessContext( 305 | VOID) 306 | { 307 | if (IsProcessDPIAware()) { 308 | return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE; 309 | } else { 310 | return DPI_AWARENESS_CONTEXT_UNAWARE; 311 | } 312 | } 313 | 314 | static DPI_AWARENESS_CONTEXT GetWindowDpiAwarenessContext( 315 | IN HWND Window) 316 | { 317 | ULONG WindowThreadId; 318 | ULONG WindowProcessId; 319 | 320 | WindowThreadId = GetWindowThreadProcessId(Window, &WindowProcessId); 321 | if (!WindowThreadId) { 322 | return 0; 323 | } 324 | 325 | // looks like there's a bug in vxkex, here should be == instead of = 326 | // and if is always true 327 | // anyway I don't want to deal with Windows kernel mode structures here 328 | 329 | if (1) { //if (WindowProcessId = (ULONG) NtCurrentTeb()->ClientId.UniqueProcess) { 330 | return vxkex::GetThreadDpiAwarenessContext(); 331 | } 332 | 333 | return DPI_AWARENESS_CONTEXT_UNAWARE; 334 | } 335 | 336 | // pointer.c 337 | 338 | static BOOL GetPointerType( 339 | IN UINT32 PointerId, 340 | OUT POINTER_INPUT_TYPE *PointerType) 341 | { 342 | *PointerType = PT_MOUSE; 343 | return TRUE; 344 | } 345 | 346 | static BOOL GetPointerFrameTouchInfo( 347 | IN UINT32 PointerId, 348 | IN OUT UINT32 *PointerCount, 349 | OUT LPVOID TouchInfo) 350 | { 351 | return FALSE; 352 | } 353 | 354 | static BOOL GetPointerFrameTouchInfoHistory( 355 | IN UINT32 PointerId, 356 | IN OUT UINT32 *EntriesCount, 357 | IN OUT UINT32 *PointerCount, 358 | OUT LPVOID TouchInfo) 359 | { 360 | return FALSE; 361 | } 362 | 363 | static BOOL GetPointerPenInfo( 364 | IN UINT32 PointerId, 365 | OUT LPVOID PenInfo) 366 | { 367 | return FALSE; 368 | } 369 | 370 | static BOOL GetPointerPenInfoHistory( 371 | IN UINT32 PointerId, 372 | IN OUT UINT32 *EntriesCount, 373 | OUT LPVOID PenInfo) 374 | { 375 | return FALSE; 376 | } 377 | 378 | static BOOL SkipPointerFrameMessages( 379 | IN UINT32 PointerId) 380 | { 381 | return TRUE; 382 | } 383 | 384 | static BOOL GetPointerDeviceRects( 385 | IN HANDLE Device, 386 | OUT LPRECT PointerDeviceRect, 387 | OUT LPRECT DisplayRect) 388 | { 389 | PointerDeviceRect->top = 0; 390 | PointerDeviceRect->left = 0; 391 | PointerDeviceRect->bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN); 392 | PointerDeviceRect->right = GetSystemMetrics(SM_CXVIRTUALSCREEN); 393 | 394 | DisplayRect->top = 0; 395 | DisplayRect->left = 0; 396 | DisplayRect->bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN); 397 | DisplayRect->right = GetSystemMetrics(SM_CXVIRTUALSCREEN); 398 | 399 | return TRUE; 400 | } 401 | 402 | static BOOL GetPointerInfo( 403 | IN DWORD PointerId, 404 | OUT POINTER_INFO *PointerInfo) 405 | { 406 | PointerInfo->pointerType = PT_MOUSE; 407 | PointerInfo->pointerId = PointerId; 408 | PointerInfo->frameId = 0; 409 | PointerInfo->pointerFlags = POINTER_FLAG_NONE; 410 | PointerInfo->sourceDevice = NULL; 411 | PointerInfo->hwndTarget = NULL; 412 | GetCursorPos(&PointerInfo->ptPixelLocation); 413 | GetCursorPos(&PointerInfo->ptHimetricLocation); 414 | GetCursorPos(&PointerInfo->ptPixelLocationRaw); 415 | GetCursorPos(&PointerInfo->ptHimetricLocationRaw); 416 | PointerInfo->dwTime = 0; 417 | PointerInfo->historyCount = 1; 418 | PointerInfo->InputData = 0; 419 | PointerInfo->dwKeyStates = 0; 420 | PointerInfo->PerformanceCount = 0; 421 | PointerInfo->ButtonChangeType = POINTER_CHANGE_NONE; 422 | 423 | return TRUE; 424 | } 425 | 426 | } // namespace vxkex 427 | -------------------------------------------------------------------------------- /qtbase/src/corelib/thread/qthread_win.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qthread.h" 5 | #include "qthread_p.h" 6 | #include "qthreadstorage.h" 7 | #include "qmutex.h" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include "qloggingcategory.h" 15 | 16 | #include 17 | 18 | #ifndef _MT 19 | # define _MT 20 | #endif // _MT 21 | #include 22 | 23 | extern "C" { 24 | // MinGW is missing the declaration of SetThreadDescription: 25 | WINBASEAPI 26 | HRESULT 27 | WINAPI 28 | SetThreadDescription( 29 | _In_ HANDLE hThread, 30 | _In_ PCWSTR lpThreadDescription 31 | ); 32 | } 33 | 34 | QT_BEGIN_NAMESPACE 35 | 36 | #if QT_CONFIG(thread) 37 | 38 | void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread); 39 | DWORD WINAPI qt_adopted_thread_watcher_function(LPVOID); 40 | 41 | static DWORD qt_current_thread_data_tls_index = TLS_OUT_OF_INDEXES; 42 | void qt_create_tls() 43 | { 44 | if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES) 45 | return; 46 | Q_CONSTINIT static QBasicMutex mutex; 47 | QMutexLocker locker(&mutex); 48 | if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES) 49 | return; 50 | qt_current_thread_data_tls_index = TlsAlloc(); 51 | } 52 | 53 | static void qt_free_tls() 54 | { 55 | if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES) { 56 | TlsFree(qt_current_thread_data_tls_index); 57 | qt_current_thread_data_tls_index = TLS_OUT_OF_INDEXES; 58 | } 59 | } 60 | Q_DESTRUCTOR_FUNCTION(qt_free_tls) 61 | 62 | /* 63 | QThreadData 64 | */ 65 | void QThreadData::clearCurrentThreadData() 66 | { 67 | TlsSetValue(qt_current_thread_data_tls_index, 0); 68 | } 69 | 70 | QThreadData *QThreadData::current(bool createIfNecessary) 71 | { 72 | qt_create_tls(); 73 | QThreadData *threadData = reinterpret_cast(TlsGetValue(qt_current_thread_data_tls_index)); 74 | if (!threadData && createIfNecessary) { 75 | threadData = new QThreadData; 76 | // This needs to be called prior to new AdoptedThread() to 77 | // avoid recursion. 78 | TlsSetValue(qt_current_thread_data_tls_index, threadData); 79 | QT_TRY { 80 | threadData->thread.storeRelease(new QAdoptedThread(threadData)); 81 | } QT_CATCH(...) { 82 | TlsSetValue(qt_current_thread_data_tls_index, 0); 83 | threadData->deref(); 84 | threadData = 0; 85 | QT_RETHROW; 86 | } 87 | threadData->deref(); 88 | threadData->isAdopted = true; 89 | threadData->threadId.storeRelaxed(reinterpret_cast(quintptr(GetCurrentThreadId()))); 90 | 91 | if (!QCoreApplicationPrivate::theMainThreadId) { 92 | auto *mainThread = threadData->thread.loadRelaxed(); 93 | mainThread->setObjectName("Qt mainThread"); 94 | QCoreApplicationPrivate::theMainThread.storeRelease(mainThread); 95 | QCoreApplicationPrivate::theMainThreadId.storeRelaxed(threadData->threadId.loadRelaxed()); 96 | } else { 97 | HANDLE realHandle = INVALID_HANDLE_VALUE; 98 | DuplicateHandle(GetCurrentProcess(), 99 | GetCurrentThread(), 100 | GetCurrentProcess(), 101 | &realHandle, 102 | 0, 103 | FALSE, 104 | DUPLICATE_SAME_ACCESS); 105 | qt_watch_adopted_thread(realHandle, threadData->thread.loadRelaxed()); 106 | } 107 | } 108 | return threadData; 109 | } 110 | 111 | void QAdoptedThread::init() 112 | { 113 | d_func()->handle = GetCurrentThread(); 114 | d_func()->id = GetCurrentThreadId(); 115 | } 116 | 117 | static QList qt_adopted_thread_handles; 118 | static QList qt_adopted_qthreads; 119 | Q_CONSTINIT static QBasicMutex qt_adopted_thread_watcher_mutex; 120 | static DWORD qt_adopted_thread_watcher_id = 0; 121 | static HANDLE qt_adopted_thread_wakeup = 0; 122 | 123 | /*! 124 | \internal 125 | Adds an adopted thread to the list of threads that Qt watches to make sure 126 | the thread data is properly cleaned up. This function starts the watcher 127 | thread if necessary. 128 | */ 129 | void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread) 130 | { 131 | QMutexLocker lock(&qt_adopted_thread_watcher_mutex); 132 | 133 | if (GetCurrentThreadId() == qt_adopted_thread_watcher_id) { 134 | CloseHandle(adoptedThreadHandle); 135 | return; 136 | } 137 | 138 | qt_adopted_thread_handles.append(adoptedThreadHandle); 139 | qt_adopted_qthreads.append(qthread); 140 | 141 | // Start watcher thread if it is not already running. 142 | if (qt_adopted_thread_watcher_id == 0) { 143 | if (qt_adopted_thread_wakeup == 0) { 144 | qt_adopted_thread_wakeup = CreateEvent(0, false, false, 0); 145 | qt_adopted_thread_handles.prepend(qt_adopted_thread_wakeup); 146 | } 147 | 148 | CloseHandle(CreateThread(0, 0, qt_adopted_thread_watcher_function, 0, 0, &qt_adopted_thread_watcher_id)); 149 | } else { 150 | SetEvent(qt_adopted_thread_wakeup); 151 | } 152 | } 153 | 154 | /* 155 | This function loops and waits for native adopted threads to finish. 156 | When this happens it derefs the QThreadData for the adopted thread 157 | to make sure it gets cleaned up properly. 158 | */ 159 | DWORD WINAPI qt_adopted_thread_watcher_function(LPVOID) 160 | { 161 | forever { 162 | qt_adopted_thread_watcher_mutex.lock(); 163 | 164 | if (qt_adopted_thread_handles.count() == 1) { 165 | qt_adopted_thread_watcher_id = 0; 166 | qt_adopted_thread_watcher_mutex.unlock(); 167 | break; 168 | } 169 | 170 | QList handlesCopy = qt_adopted_thread_handles; 171 | qt_adopted_thread_watcher_mutex.unlock(); 172 | 173 | DWORD ret = WAIT_TIMEOUT; 174 | int count; 175 | int offset; 176 | int loops = handlesCopy.size() / MAXIMUM_WAIT_OBJECTS; 177 | if (handlesCopy.size() % MAXIMUM_WAIT_OBJECTS) 178 | ++loops; 179 | if (loops == 1) { 180 | // no need to loop, no timeout 181 | offset = 0; 182 | count = handlesCopy.count(); 183 | ret = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE); 184 | } else { 185 | int loop = 0; 186 | do { 187 | offset = loop * MAXIMUM_WAIT_OBJECTS; 188 | count = qMin(handlesCopy.count() - offset, MAXIMUM_WAIT_OBJECTS); 189 | ret = WaitForMultipleObjects(count, handlesCopy.constData() + offset, false, 100); 190 | loop = (loop + 1) % loops; 191 | } while (ret == WAIT_TIMEOUT); 192 | } 193 | 194 | if (ret == WAIT_FAILED || ret >= WAIT_OBJECT_0 + uint(count)) { 195 | qWarning("QThread internal error while waiting for adopted threads: %d", int(GetLastError())); 196 | continue; 197 | } 198 | 199 | const int handleIndex = offset + ret - WAIT_OBJECT_0; 200 | if (handleIndex == 0) // New handle to watch was added. 201 | continue; 202 | const int qthreadIndex = handleIndex - 1; 203 | 204 | qt_adopted_thread_watcher_mutex.lock(); 205 | QThreadData *data = QThreadData::get2(qt_adopted_qthreads.at(qthreadIndex)); 206 | qt_adopted_thread_watcher_mutex.unlock(); 207 | if (data->isAdopted) { 208 | QThread *thread = data->thread; 209 | Q_ASSERT(thread); 210 | auto thread_p = static_cast(QObjectPrivate::get(thread)); 211 | Q_UNUSED(thread_p); 212 | Q_ASSERT(!thread_p->finished); 213 | thread_p->finish(); 214 | } 215 | data->deref(); 216 | 217 | QMutexLocker lock(&qt_adopted_thread_watcher_mutex); 218 | CloseHandle(qt_adopted_thread_handles.at(handleIndex)); 219 | qt_adopted_thread_handles.remove(handleIndex); 220 | qt_adopted_qthreads.remove(qthreadIndex); 221 | } 222 | 223 | QThreadData *threadData = reinterpret_cast(TlsGetValue(qt_current_thread_data_tls_index)); 224 | if (threadData) 225 | threadData->deref(); 226 | 227 | return 0; 228 | } 229 | 230 | #ifndef Q_OS_WIN64 231 | # define ULONG_PTR DWORD 232 | #endif 233 | 234 | typedef struct tagTHREADNAME_INFO 235 | { 236 | DWORD dwType; // must be 0x1000 237 | LPCSTR szName; // pointer to name (in user addr space) 238 | HANDLE dwThreadID; // thread ID (-1=caller thread) 239 | DWORD dwFlags; // reserved for future use, must be zero 240 | } THREADNAME_INFO; 241 | 242 | typedef HRESULT(WINAPI* SetThreadDescriptionFunc)(HANDLE, PCWSTR); 243 | 244 | #if defined(Q_CC_MSVC) 245 | 246 | // Helper function to set the thread name using RaiseException and __try 247 | static void setThreadNameUsingException(HANDLE threadId, LPCSTR threadName) { 248 | THREADNAME_INFO info; 249 | info.dwType = 0x1000; 250 | info.szName = threadName; 251 | info.dwThreadID = threadId; 252 | info.dwFlags = 0; 253 | 254 | __try { 255 | RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), 256 | reinterpret_cast(&info)); 257 | } 258 | __except (EXCEPTION_CONTINUE_EXECUTION) { 259 | } 260 | } 261 | 262 | #endif // Q_CC_MSVC 263 | 264 | void qt_set_thread_name(HANDLE threadId, const QString &name) 265 | { 266 | HMODULE hKernel32 = GetModuleHandleW(L"Kernel32.dll"); 267 | 268 | SetThreadDescriptionFunc pSetThreadDescription = 269 | reinterpret_cast(GetProcAddress(hKernel32, "SetThreadDescription")); 270 | 271 | if (pSetThreadDescription) { 272 | pSetThreadDescription(threadId, reinterpret_cast(name.utf16()) ); 273 | } 274 | else { 275 | 276 | #if defined(Q_CC_MSVC) 277 | 278 | std::string stdStr = name.toStdString(); 279 | LPCSTR threadName = stdStr.c_str(); 280 | setThreadNameUsingException(threadId, threadName); 281 | 282 | #endif // Q_CC_MSVC 283 | } 284 | } 285 | 286 | /************************************************************************** 287 | ** QThreadPrivate 288 | *************************************************************************/ 289 | 290 | #endif // QT_CONFIG(thread) 291 | 292 | QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data) 293 | { 294 | Q_UNUSED(data); 295 | return new QEventDispatcherWin32; 296 | } 297 | 298 | #if QT_CONFIG(thread) 299 | 300 | unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg) noexcept 301 | { 302 | QThread *thr = reinterpret_cast(arg); 303 | QThreadData *data = QThreadData::get2(thr); 304 | 305 | qt_create_tls(); 306 | TlsSetValue(qt_current_thread_data_tls_index, data); 307 | data->threadId.storeRelaxed(reinterpret_cast(quintptr(GetCurrentThreadId()))); 308 | 309 | QThread::setTerminationEnabled(false); 310 | 311 | { 312 | QMutexLocker locker(&thr->d_func()->mutex); 313 | data->quitNow = thr->d_func()->exited; 314 | } 315 | 316 | data->ensureEventDispatcher(); 317 | data->eventDispatcher.loadRelaxed()->startingUp(); 318 | 319 | // sets the name of the current thread. 320 | QString threadName = std::exchange(thr->d_func()->objectName, {}); 321 | if (Q_LIKELY(threadName.isEmpty())) 322 | threadName = QString::fromUtf8(thr->metaObject()->className()); 323 | qt_set_thread_name(GetCurrentThread(), threadName); 324 | 325 | emit thr->started(QThread::QPrivateSignal()); 326 | QThread::setTerminationEnabled(true); 327 | thr->run(); 328 | 329 | thr->d_func()->finish(); 330 | return 0; 331 | } 332 | 333 | /* 334 | For regularly terminating threads, this will be called and executed by the thread as the 335 | last code before the thread exits. In that case, \a arg is the current QThread. 336 | 337 | However, this function will also be called by QThread::terminate (as well as wait() and 338 | setTerminationEnabled) to give Qt a chance to update the terminated thread's state and 339 | process pending DeleteLater events for objects that live in the terminated thread. And for 340 | adopted thread, this method is called by the thread watcher. 341 | 342 | In those cases, \a arg will not be the current thread. 343 | */ 344 | void QThreadPrivate::finish(bool lockAnyway) noexcept 345 | { 346 | QThreadPrivate *d = this; 347 | QThread *thr = q_func(); 348 | 349 | QMutexLocker locker(lockAnyway ? &d->mutex : nullptr); 350 | d->isInFinish = true; 351 | d->priority = QThread::InheritPriority; 352 | void **tls_data = reinterpret_cast(&d->data->tls); 353 | if (lockAnyway) 354 | locker.unlock(); 355 | emit thr->finished(QThread::QPrivateSignal()); 356 | QCoreApplicationPrivate::sendPostedEvents(nullptr, QEvent::DeferredDelete, d->data); 357 | QThreadStorageData::finish(tls_data); 358 | if (lockAnyway) 359 | locker.relock(); 360 | 361 | QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher.loadRelaxed(); 362 | if (eventDispatcher) { 363 | d->data->eventDispatcher = 0; 364 | if (lockAnyway) 365 | locker.unlock(); 366 | eventDispatcher->closingDown(); 367 | delete eventDispatcher; 368 | if (lockAnyway) 369 | locker.relock(); 370 | } 371 | 372 | d->running = false; 373 | d->finished = true; 374 | d->isInFinish = false; 375 | d->interruptionRequested.store(false, std::memory_order_relaxed); 376 | 377 | if (!d->waiters) { 378 | CloseHandle(d->handle); 379 | d->handle = 0; 380 | } 381 | 382 | d->id = 0; 383 | } 384 | 385 | /************************************************************************** 386 | ** QThread 387 | *************************************************************************/ 388 | 389 | Qt::HANDLE QThread::currentThreadIdImpl() noexcept 390 | { 391 | return reinterpret_cast(quintptr(GetCurrentThreadId())); 392 | } 393 | 394 | int QThread::idealThreadCount() noexcept 395 | { 396 | SYSTEM_INFO sysinfo; 397 | GetSystemInfo(&sysinfo); 398 | return sysinfo.dwNumberOfProcessors; 399 | } 400 | 401 | void QThread::yieldCurrentThread() 402 | { 403 | SwitchToThread(); 404 | } 405 | 406 | #endif // QT_CONFIG(thread) 407 | 408 | void QThread::sleep(std::chrono::nanoseconds nsecs) 409 | { 410 | using namespace std::chrono; 411 | ::Sleep(DWORD(duration_cast(nsecs).count())); 412 | } 413 | 414 | void QThread::sleep(unsigned long secs) 415 | { 416 | ::Sleep(secs * 1000); 417 | } 418 | 419 | void QThread::msleep(unsigned long msecs) 420 | { 421 | ::Sleep(msecs); 422 | } 423 | 424 | void QThread::usleep(unsigned long usecs) 425 | { 426 | ::Sleep((usecs / 1000) + 1); 427 | } 428 | 429 | #if QT_CONFIG(thread) 430 | 431 | void QThread::start(Priority priority) 432 | { 433 | Q_D(QThread); 434 | QMutexLocker locker(&d->mutex); 435 | 436 | if (d->isInFinish) { 437 | locker.unlock(); 438 | wait(); 439 | locker.relock(); 440 | } 441 | 442 | if (d->running) 443 | return; 444 | 445 | // avoid interacting with the binding system 446 | d->objectName = d->extraData ? d->extraData->objectName.valueBypassingBindings() 447 | : QString(); 448 | d->running = true; 449 | d->finished = false; 450 | d->exited = false; 451 | d->returnCode = 0; 452 | d->interruptionRequested.store(false, std::memory_order_relaxed); 453 | 454 | /* 455 | NOTE: we create the thread in the suspended state, set the 456 | priority and then resume the thread. 457 | 458 | since threads are created with normal priority by default, we 459 | could get into a case where a thread (with priority less than 460 | NormalPriority) tries to create a new thread (also with priority 461 | less than NormalPriority), but the newly created thread preempts 462 | its 'parent' and runs at normal priority. 463 | */ 464 | #if defined(Q_CC_MSVC) && !defined(_DLL) 465 | // MSVC -MT or -MTd build 466 | d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start, 467 | this, CREATE_SUSPENDED, &(d->id)); 468 | #else 469 | // MSVC -MD or -MDd or MinGW build 470 | d->handle = CreateThread(nullptr, d->stackSize, 471 | reinterpret_cast(QThreadPrivate::start), 472 | this, CREATE_SUSPENDED, reinterpret_cast(&d->id)); 473 | #endif 474 | 475 | if (!d->handle) { 476 | qErrnoWarning("QThread::start: Failed to create thread"); 477 | d->running = false; 478 | d->finished = true; 479 | return; 480 | } 481 | 482 | int prio; 483 | d->priority = priority; 484 | switch (priority) { 485 | case IdlePriority: 486 | prio = THREAD_PRIORITY_IDLE; 487 | break; 488 | 489 | case LowestPriority: 490 | prio = THREAD_PRIORITY_LOWEST; 491 | break; 492 | 493 | case LowPriority: 494 | prio = THREAD_PRIORITY_BELOW_NORMAL; 495 | break; 496 | 497 | case NormalPriority: 498 | prio = THREAD_PRIORITY_NORMAL; 499 | break; 500 | 501 | case HighPriority: 502 | prio = THREAD_PRIORITY_ABOVE_NORMAL; 503 | break; 504 | 505 | case HighestPriority: 506 | prio = THREAD_PRIORITY_HIGHEST; 507 | break; 508 | 509 | case TimeCriticalPriority: 510 | prio = THREAD_PRIORITY_TIME_CRITICAL; 511 | break; 512 | 513 | case InheritPriority: 514 | default: 515 | prio = GetThreadPriority(GetCurrentThread()); 516 | break; 517 | } 518 | 519 | if (!SetThreadPriority(d->handle, prio)) { 520 | qErrnoWarning("QThread::start: Failed to set thread priority"); 521 | } 522 | 523 | if (ResumeThread(d->handle) == (DWORD) -1) { 524 | qErrnoWarning("QThread::start: Failed to resume new thread"); 525 | } 526 | } 527 | 528 | void QThread::terminate() 529 | { 530 | Q_D(QThread); 531 | QMutexLocker locker(&d->mutex); 532 | if (!d->running) 533 | return; 534 | if (!d->terminationEnabled) { 535 | d->terminatePending = true; 536 | return; 537 | } 538 | 539 | TerminateThread(d->handle, 0); 540 | d->finish(false); 541 | } 542 | 543 | bool QThread::wait(QDeadlineTimer deadline) 544 | { 545 | Q_D(QThread); 546 | QMutexLocker locker(&d->mutex); 547 | 548 | if (d->id == GetCurrentThreadId()) { 549 | qWarning("QThread::wait: Thread tried to wait on itself"); 550 | return false; 551 | } 552 | if (d->finished || !d->running) 553 | return true; 554 | return d->wait(locker, deadline); 555 | } 556 | 557 | bool QThreadPrivate::wait(QMutexLocker &locker, QDeadlineTimer deadline) 558 | { 559 | Q_ASSERT(locker.isLocked()); 560 | QThreadPrivate *d = this; 561 | 562 | ++d->waiters; 563 | locker.mutex()->unlock(); 564 | 565 | bool ret = false; 566 | switch (WaitForSingleObject(d->handle, deadline.remainingTime())) { 567 | case WAIT_OBJECT_0: 568 | ret = true; 569 | break; 570 | case WAIT_FAILED: 571 | qErrnoWarning("QThread::wait: Thread wait failure"); 572 | break; 573 | case WAIT_ABANDONED: 574 | case WAIT_TIMEOUT: 575 | default: 576 | break; 577 | } 578 | 579 | locker.mutex()->lock(); 580 | --d->waiters; 581 | 582 | if (ret && !d->finished) { 583 | // thread was terminated by someone else 584 | 585 | d->finish(false); 586 | } 587 | 588 | if (d->finished && !d->waiters) { 589 | CloseHandle(d->handle); 590 | d->handle = 0; 591 | } 592 | 593 | return ret; 594 | } 595 | 596 | void QThread::setTerminationEnabled(bool enabled) 597 | { 598 | QThread *thr = currentThread(); 599 | Q_ASSERT_X(thr != 0, "QThread::setTerminationEnabled()", 600 | "Current thread was not started with QThread."); 601 | QThreadPrivate *d = thr->d_func(); 602 | QMutexLocker locker(&d->mutex); 603 | d->terminationEnabled = enabled; 604 | if (enabled && d->terminatePending) { 605 | d->finish(false); 606 | locker.unlock(); // don't leave the mutex locked! 607 | _endthreadex(0); 608 | } 609 | } 610 | 611 | // Caller must hold the mutex 612 | void QThreadPrivate::setPriority(QThread::Priority threadPriority) 613 | { 614 | // copied from start() with a few modifications: 615 | 616 | int prio; 617 | priority = threadPriority; 618 | switch (threadPriority) { 619 | case QThread::IdlePriority: 620 | prio = THREAD_PRIORITY_IDLE; 621 | break; 622 | 623 | case QThread::LowestPriority: 624 | prio = THREAD_PRIORITY_LOWEST; 625 | break; 626 | 627 | case QThread::LowPriority: 628 | prio = THREAD_PRIORITY_BELOW_NORMAL; 629 | break; 630 | 631 | case QThread::NormalPriority: 632 | prio = THREAD_PRIORITY_NORMAL; 633 | break; 634 | 635 | case QThread::HighPriority: 636 | prio = THREAD_PRIORITY_ABOVE_NORMAL; 637 | break; 638 | 639 | case QThread::HighestPriority: 640 | prio = THREAD_PRIORITY_HIGHEST; 641 | break; 642 | 643 | case QThread::TimeCriticalPriority: 644 | prio = THREAD_PRIORITY_TIME_CRITICAL; 645 | break; 646 | 647 | default: 648 | return; 649 | } 650 | 651 | if (!SetThreadPriority(handle, prio)) { 652 | qErrnoWarning("QThread::setPriority: Failed to set thread priority"); 653 | } 654 | } 655 | 656 | #endif // QT_CONFIG(thread) 657 | 658 | QT_END_NAMESPACE 659 | -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // Copyright (C) 2013 Samuel Gaist 3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 4 | 5 | #include "qwindowsintegration.h" 6 | #include "qwindowswindow.h" 7 | #include "qwindowscontext.h" 8 | #include "qwin10helpers.h" 9 | #include "qwindowsmenu.h" 10 | #include "qwindowsopenglcontext.h" 11 | 12 | #include "qwindowsscreen.h" 13 | #include "qwindowstheme.h" 14 | #include "qwindowsservices.h" 15 | #include 16 | #if QT_CONFIG(directwrite3) 17 | #include 18 | #endif 19 | #ifndef QT_NO_FREETYPE 20 | # include 21 | #endif 22 | #include 23 | #if QT_CONFIG(clipboard) 24 | # include "qwindowsclipboard.h" 25 | # if QT_CONFIG(draganddrop) 26 | # include "qwindowsdrag.h" 27 | # endif 28 | #endif 29 | #include "qwindowsinputcontext.h" 30 | #include "qwindowskeymapper.h" 31 | #if QT_CONFIG(accessibility) 32 | # include "uiautomation/qwindowsuiaaccessibility.h" 33 | #endif 34 | 35 | #include 36 | #include 37 | #if QT_CONFIG(sessionmanager) 38 | # include "qwindowssessionmanager.h" 39 | #endif 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include 47 | 48 | #include 49 | #include 50 | 51 | #include 52 | #include 53 | 54 | #include 55 | 56 | #include 57 | 58 | #if !defined(QT_NO_OPENGL) 59 | # include "qwindowsglcontext.h" 60 | #endif 61 | 62 | #include "qwindowsopengltester.h" 63 | 64 | #if QT_CONFIG(cpp_winrt) 65 | # include 66 | # include 67 | # include 68 | # include 69 | # include 70 | #endif 71 | 72 | #include 73 | 74 | static inline void initOpenGlBlacklistResources() 75 | { 76 | Q_INIT_RESOURCE(openglblacklists); 77 | } 78 | 79 | QT_BEGIN_NAMESPACE 80 | 81 | using namespace Qt::StringLiterals; 82 | 83 | struct QWindowsIntegrationPrivate 84 | { 85 | Q_DISABLE_COPY_MOVE(QWindowsIntegrationPrivate) 86 | explicit QWindowsIntegrationPrivate() = default; 87 | ~QWindowsIntegrationPrivate(); 88 | 89 | void parseOptions(QWindowsIntegration *q, const QStringList ¶mList); 90 | 91 | unsigned m_options = 0; 92 | QWindowsContext m_context; 93 | QPlatformFontDatabase *m_fontDatabase = nullptr; 94 | #if QT_CONFIG(clipboard) 95 | QWindowsClipboard m_clipboard; 96 | # if QT_CONFIG(draganddrop) 97 | QWindowsDrag m_drag; 98 | # endif 99 | #endif 100 | #ifndef QT_NO_OPENGL 101 | QMutex m_staticContextLock; 102 | QScopedPointer m_staticOpenGLContext; 103 | #endif // QT_NO_OPENGL 104 | QScopedPointer m_inputContext; 105 | #if QT_CONFIG(accessibility) 106 | QWindowsUiaAccessibility m_accessibility; 107 | #endif 108 | mutable QScopedPointer m_services; 109 | }; 110 | 111 | template 112 | bool parseIntOption(const QString ¶meter,const QLatin1StringView &option, 113 | IntType minimumValue, IntType maximumValue, IntType *target) 114 | { 115 | const int valueLength = parameter.size() - option.size() - 1; 116 | if (valueLength < 1 || !parameter.startsWith(option) || parameter.at(option.size()) != u'=') 117 | return false; 118 | bool ok; 119 | const auto valueRef = QStringView{parameter}.right(valueLength); 120 | const int value = valueRef.toInt(&ok); 121 | if (ok) { 122 | if (value >= int(minimumValue) && value <= int(maximumValue)) 123 | *target = static_cast(value); 124 | else { 125 | qWarning() << "Value" << value << "for option" << option << "out of range" 126 | << minimumValue << ".." << maximumValue; 127 | } 128 | } else { 129 | qWarning() << "Invalid value" << valueRef << "for option" << option; 130 | } 131 | return true; 132 | } 133 | 134 | using DarkModeHandlingFlag = QNativeInterface::Private::QWindowsApplication::DarkModeHandlingFlag; 135 | using DarkModeHandling = QNativeInterface::Private::QWindowsApplication::DarkModeHandling; 136 | 137 | static inline unsigned parseOptions(const QStringList ¶mList, 138 | int *tabletAbsoluteRange, 139 | QtWindows::DpiAwareness *dpiAwareness, 140 | DarkModeHandling *darkModeHandling) 141 | { 142 | unsigned options = 0; 143 | 144 | for (const QString ¶m : paramList) { 145 | if (param.startsWith(u"fontengine=")) { 146 | if (param.endsWith(u"gdi")) { 147 | options |= QWindowsIntegration::FontDatabaseGDI; 148 | } else if (param.endsWith(u"freetype")) { 149 | options |= QWindowsIntegration::FontDatabaseFreeType; 150 | } else if (param.endsWith(u"native")) { 151 | options |= QWindowsIntegration::FontDatabaseNative; 152 | } 153 | } else if (param.startsWith(u"dialogs=")) { 154 | if (param.endsWith(u"xp")) { 155 | options |= QWindowsIntegration::XpNativeDialogs; 156 | } else if (param.endsWith(u"none")) { 157 | options |= QWindowsIntegration::NoNativeDialogs; 158 | } 159 | } else if (param == u"altgr") { 160 | options |= QWindowsIntegration::DetectAltGrModifier; 161 | } else if (param == u"gl=gdi") { 162 | options |= QWindowsIntegration::DisableArb; 163 | } else if (param == u"nodirectwrite") { 164 | options |= QWindowsIntegration::DontUseDirectWriteFonts; 165 | } else if (param == u"nocolorfonts") { 166 | options |= QWindowsIntegration::DontUseColorFonts; 167 | } else if (param == u"nomousefromtouch") { 168 | options |= QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch; 169 | } else if (parseIntOption(param, "verbose"_L1, 0, INT_MAX, &QWindowsContext::verbose) 170 | || parseIntOption(param, "tabletabsoluterange"_L1, 0, INT_MAX, tabletAbsoluteRange) 171 | || parseIntOption(param, "dpiawareness"_L1, QtWindows::DpiAwareness::Invalid, 172 | QtWindows::DpiAwareness::PerMonitorVersion2, dpiAwareness)) { 173 | } else if (param == u"menus=native") { 174 | options |= QWindowsIntegration::AlwaysUseNativeMenus; 175 | } else if (param == u"menus=none") { 176 | options |= QWindowsIntegration::NoNativeMenus; 177 | } else if (param == u"reverse") { 178 | options |= QWindowsIntegration::RtlEnabled; 179 | } else if (param == u"darkmode=0") { 180 | *darkModeHandling = {}; 181 | } else if (param == u"darkmode=1") { 182 | darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeWindowFrames); 183 | darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeStyle, false); 184 | } else if (param == u"darkmode=2") { 185 | darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeWindowFrames); 186 | darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeStyle); 187 | } else { 188 | qWarning() << "Unknown option" << param; 189 | } 190 | } 191 | return options; 192 | } 193 | 194 | void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStringList ¶mList) 195 | { 196 | initOpenGlBlacklistResources(); 197 | 198 | static bool dpiAwarenessSet = false; 199 | // Default to per-monitor-v2 awareness (if available) 200 | QtWindows::DpiAwareness dpiAwareness = QtWindows::DpiAwareness::PerMonitorVersion2; 201 | 202 | int tabletAbsoluteRange = -1; 203 | DarkModeHandling darkModeHandling = DarkModeHandlingFlag::DarkModeWindowFrames 204 | | DarkModeHandlingFlag::DarkModeStyle; 205 | m_options = ::parseOptions(paramList, &tabletAbsoluteRange, &dpiAwareness, &darkModeHandling); 206 | q->setDarkModeHandling(darkModeHandling); 207 | QWindowsFontDatabase::setFontOptions(m_options); 208 | if (tabletAbsoluteRange >= 0) 209 | QWindowsContext::setTabletAbsoluteRange(tabletAbsoluteRange); 210 | 211 | QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); 212 | QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); 213 | 214 | if (!dpiAwarenessSet) { // Set only once in case of repeated instantiations of QGuiApplication. 215 | if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) { 216 | m_context.setProcessDpiAwareness(dpiAwareness); 217 | qCDebug(lcQpaWindow) << "DpiAwareness=" << dpiAwareness 218 | << "effective process DPI awareness=" << QWindowsContext::processDpiAwareness(); 219 | } 220 | dpiAwarenessSet = true; 221 | } 222 | 223 | m_context.initTouch(m_options); 224 | QPlatformCursor::setCapability(QPlatformCursor::OverrideCursor); 225 | 226 | m_context.initPowerNotificationHandler(); 227 | } 228 | 229 | QWindowsIntegrationPrivate::~QWindowsIntegrationPrivate() 230 | { 231 | delete m_fontDatabase; 232 | } 233 | 234 | QWindowsIntegration *QWindowsIntegration::m_instance = nullptr; 235 | 236 | QWindowsIntegration::QWindowsIntegration(const QStringList ¶mList) : 237 | d(new QWindowsIntegrationPrivate) 238 | { 239 | m_instance = this; 240 | d->parseOptions(this, paramList); 241 | #if QT_CONFIG(clipboard) 242 | d->m_clipboard.registerViewer(); 243 | #endif 244 | d->m_context.screenManager().initialize(); 245 | d->m_context.setDetectAltGrModifier((d->m_options & DetectAltGrModifier) != 0); 246 | } 247 | 248 | QWindowsIntegration::~QWindowsIntegration() 249 | { 250 | m_instance = nullptr; 251 | } 252 | 253 | void QWindowsIntegration::initialize() 254 | { 255 | auto icStrs = QPlatformInputContextFactory::requested(); 256 | icStrs.isEmpty() ? d->m_inputContext.reset(new QWindowsInputContext) 257 | : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStrs)); 258 | } 259 | 260 | bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) const 261 | { 262 | switch (cap) { 263 | case ThreadedPixmaps: 264 | return true; 265 | #ifndef QT_NO_OPENGL 266 | case OpenGL: 267 | return true; 268 | case ThreadedOpenGL: 269 | if (const QWindowsStaticOpenGLContext *glContext = QWindowsIntegration::staticOpenGLContext()) 270 | return glContext->supportsThreadedOpenGL(); 271 | return false; 272 | #endif // !QT_NO_OPENGL 273 | case WindowMasks: 274 | return true; 275 | case MultipleWindows: 276 | return true; 277 | case ForeignWindows: 278 | return true; 279 | case RasterGLSurface: 280 | return true; 281 | case AllGLFunctionsQueryable: 282 | return true; 283 | case SwitchableWidgetComposition: 284 | return false; // QTBUG-68329 QTBUG-53515 QTBUG-54734 285 | case BackingStoreStaticContents: 286 | return true; 287 | default: 288 | return QPlatformIntegration::hasCapability(cap); 289 | } 290 | return false; 291 | } 292 | 293 | QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) const 294 | { 295 | if (window->type() == Qt::Desktop) { 296 | auto *result = new QWindowsDesktopWindow(window); 297 | qCDebug(lcQpaWindow) << "Desktop window:" << window 298 | << Qt::showbase << Qt::hex << result->winId() << Qt::noshowbase << Qt::dec << result->geometry(); 299 | return result; 300 | } 301 | 302 | QWindowsWindowData requested; 303 | requested.flags = window->flags(); 304 | requested.geometry = window->isTopLevel() 305 | ? QHighDpi::toNativePixels(window->geometry(), window) 306 | : QHighDpi::toNativeLocalPosition(window->geometry(), window); 307 | if (!(requested.flags & Qt::FramelessWindowHint)) { 308 | // Apply custom margins (see QWindowsWindow::setCustomMargins())). 309 | const QVariant customMarginsV = window->property("_q_windowsCustomMargins"); 310 | if (customMarginsV.isValid()) 311 | requested.customMargins = qvariant_cast(customMarginsV); 312 | } 313 | 314 | QWindowsWindowData obtained = 315 | QWindowsWindowData::create(window, requested, 316 | QWindowsWindow::formatWindowTitle(window->title())); 317 | qCDebug(lcQpaWindow).nospace() 318 | << __FUNCTION__ << ' ' << window 319 | << "\n Requested: " << requested.geometry << " frame incl.=" 320 | << QWindowsGeometryHint::positionIncludesFrame(window) 321 | << ' ' << requested.flags 322 | << "\n Obtained : " << obtained.geometry << " margins=" << obtained.fullFrameMargins 323 | << " handle=" << obtained.hwnd << ' ' << obtained.flags << '\n'; 324 | 325 | if (Q_UNLIKELY(!obtained.hwnd)) 326 | return nullptr; 327 | 328 | QWindowsWindow *result = createPlatformWindowHelper(window, obtained); 329 | Q_ASSERT(result); 330 | 331 | if (window->isTopLevel() && !QWindowsContext::shouldHaveNonClientDpiScaling(window)) 332 | result->setFlag(QWindowsWindow::DisableNonClientScaling); 333 | 334 | if (QWindowsMenuBar *menuBarToBeInstalled = QWindowsMenuBar::menuBarOf(window)) 335 | menuBarToBeInstalled->install(result); 336 | 337 | return result; 338 | } 339 | 340 | QPlatformWindow *QWindowsIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const 341 | { 342 | const HWND hwnd = reinterpret_cast(nativeHandle); 343 | if (!IsWindow(hwnd)) { 344 | qWarning("Windows QPA: Invalid foreign window ID %p.", hwnd); 345 | return nullptr; 346 | } 347 | auto *result = new QWindowsForeignWindow(window, hwnd); 348 | const QRect obtainedGeometry = result->geometry(); 349 | QScreen *screen = nullptr; 350 | if (const QPlatformScreen *pScreen = result->screenForGeometry(obtainedGeometry)) 351 | screen = pScreen->screen(); 352 | if (screen && screen != window->screen()) 353 | window->setScreen(screen); 354 | qCDebug(lcQpaWindow) << "Foreign window:" << window << Qt::showbase << Qt::hex 355 | << result->winId() << Qt::noshowbase << Qt::dec << obtainedGeometry << screen; 356 | return result; 357 | } 358 | 359 | // Overridden to return a QWindowsDirect2DWindow in Direct2D plugin. 360 | QWindowsWindow *QWindowsIntegration::createPlatformWindowHelper(QWindow *window, const QWindowsWindowData &data) const 361 | { 362 | return new QWindowsWindow(window, data); 363 | } 364 | 365 | #ifndef QT_NO_OPENGL 366 | 367 | QWindowsStaticOpenGLContext *QWindowsStaticOpenGLContext::doCreate() 368 | { 369 | #if defined(QT_OPENGL_DYNAMIC) 370 | QWindowsOpenGLTester::Renderer requestedRenderer = QWindowsOpenGLTester::requestedRenderer(); 371 | switch (requestedRenderer) { 372 | case QWindowsOpenGLTester::DesktopGl: 373 | if (QWindowsStaticOpenGLContext *glCtx = QOpenGLStaticContext::create()) { 374 | if ((QWindowsOpenGLTester::supportedRenderers(requestedRenderer) & QWindowsOpenGLTester::DisableRotationFlag) 375 | && !QWindowsScreen::setOrientationPreference(Qt::LandscapeOrientation)) { 376 | qCWarning(lcQpaGl, "Unable to disable rotation."); 377 | } 378 | return glCtx; 379 | } 380 | qCWarning(lcQpaGl, "System OpenGL failed. Falling back to Software OpenGL."); 381 | return QOpenGLStaticContext::create(true); 382 | case QWindowsOpenGLTester::SoftwareRasterizer: 383 | if (QWindowsStaticOpenGLContext *swCtx = QOpenGLStaticContext::create(true)) 384 | return swCtx; 385 | qCWarning(lcQpaGl, "Software OpenGL failed. Falling back to system OpenGL."); 386 | if (QWindowsOpenGLTester::supportedRenderers(requestedRenderer) & QWindowsOpenGLTester::DesktopGl) 387 | return QOpenGLStaticContext::create(); 388 | return nullptr; 389 | default: 390 | break; 391 | } 392 | 393 | const QWindowsOpenGLTester::Renderers supportedRenderers = QWindowsOpenGLTester::supportedRenderers(requestedRenderer); 394 | if (supportedRenderers.testFlag(QWindowsOpenGLTester::DisableProgramCacheFlag) 395 | && !QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) { 396 | QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache); 397 | } 398 | if (supportedRenderers & QWindowsOpenGLTester::DesktopGl) { 399 | if (QWindowsStaticOpenGLContext *glCtx = QOpenGLStaticContext::create()) { 400 | if ((supportedRenderers & QWindowsOpenGLTester::DisableRotationFlag) 401 | && !QWindowsScreen::setOrientationPreference(Qt::LandscapeOrientation)) { 402 | qCWarning(lcQpaGl, "Unable to disable rotation."); 403 | } 404 | return glCtx; 405 | } 406 | } 407 | return QOpenGLStaticContext::create(true); 408 | #else 409 | return QOpenGLStaticContext::create(); 410 | #endif 411 | } 412 | 413 | QWindowsStaticOpenGLContext *QWindowsStaticOpenGLContext::create() 414 | { 415 | return QWindowsStaticOpenGLContext::doCreate(); 416 | } 417 | 418 | QPlatformOpenGLContext *QWindowsIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const 419 | { 420 | qCDebug(lcQpaGl) << __FUNCTION__ << context->format(); 421 | if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { 422 | std::unique_ptr result(staticOpenGLContext->createContext(context)); 423 | if (result->isValid()) 424 | return result.release(); 425 | } 426 | return nullptr; 427 | } 428 | 429 | QOpenGLContext::OpenGLModuleType QWindowsIntegration::openGLModuleType() 430 | { 431 | #if !defined(QT_OPENGL_DYNAMIC) 432 | return QOpenGLContext::LibGL; 433 | #else 434 | if (const QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) 435 | return staticOpenGLContext->moduleType(); 436 | return QOpenGLContext::LibGL; 437 | #endif 438 | } 439 | 440 | HMODULE QWindowsIntegration::openGLModuleHandle() const 441 | { 442 | if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) 443 | return static_cast(staticOpenGLContext->moduleHandle()); 444 | 445 | return nullptr; 446 | } 447 | 448 | QOpenGLContext *QWindowsIntegration::createOpenGLContext(HGLRC ctx, HWND window, QOpenGLContext *shareContext) const 449 | { 450 | if (!ctx || !window) 451 | return nullptr; 452 | 453 | if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { 454 | std::unique_ptr result(staticOpenGLContext->createContext(ctx, window)); 455 | if (result->isValid()) { 456 | auto *context = new QOpenGLContext; 457 | context->setShareContext(shareContext); 458 | auto *contextPrivate = QOpenGLContextPrivate::get(context); 459 | contextPrivate->adopt(result.release()); 460 | return context; 461 | } 462 | } 463 | 464 | return nullptr; 465 | } 466 | 467 | QWindowsStaticOpenGLContext *QWindowsIntegration::staticOpenGLContext() 468 | { 469 | QWindowsIntegration *integration = QWindowsIntegration::instance(); 470 | if (!integration) 471 | return nullptr; 472 | QWindowsIntegrationPrivate *d = integration->d.data(); 473 | QMutexLocker lock(&d->m_staticContextLock); 474 | if (d->m_staticOpenGLContext.isNull()) 475 | d->m_staticOpenGLContext.reset(QWindowsStaticOpenGLContext::create()); 476 | return d->m_staticOpenGLContext.data(); 477 | } 478 | #endif // !QT_NO_OPENGL 479 | 480 | QPlatformFontDatabase *QWindowsIntegration::fontDatabase() const 481 | { 482 | if (!d->m_fontDatabase) { 483 | #ifndef QT_NO_FREETYPE 484 | if (d->m_options & QWindowsIntegration::FontDatabaseFreeType) 485 | d->m_fontDatabase = new QWindowsFontDatabaseFT; 486 | else 487 | #endif // QT_NO_FREETYPE 488 | #if QT_CONFIG(directwrite3) 489 | 490 | /* IDWriteFontFace3 is only reportedly available starting with Windows 10. This change is necessary starting 491 | with Qt 6.8, where DirectWrite is used by default to populate the font database. 492 | More info: https://github.com/videolan/vlc/blob/master/contrib/src/qt/0001-Use-DirectWrite-font-database-only-with-Windows-10-a.patch 493 | */ 494 | 495 | if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10 && 496 | !(d->m_options & (QWindowsIntegration::FontDatabaseGDI | QWindowsIntegration::DontUseDirectWriteFonts))) 497 | d->m_fontDatabase = new QWindowsDirectWriteFontDatabase; 498 | else 499 | #endif 500 | d->m_fontDatabase = new QWindowsFontDatabase; 501 | } 502 | return d->m_fontDatabase; 503 | } 504 | 505 | #ifdef SPI_GETKEYBOARDSPEED 506 | static inline int keyBoardAutoRepeatRateMS() 507 | { 508 | DWORD time = 0; 509 | if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &time, 0)) 510 | return time ? 1000 / static_cast(time) : 500; 511 | return 30; 512 | } 513 | #endif 514 | 515 | QVariant QWindowsIntegration::styleHint(QPlatformIntegration::StyleHint hint) const 516 | { 517 | switch (hint) { 518 | case QPlatformIntegration::CursorFlashTime: 519 | if (const unsigned timeMS = GetCaretBlinkTime()) 520 | return QVariant(timeMS != INFINITE ? int(timeMS) * 2 : 0); 521 | break; 522 | #ifdef SPI_GETKEYBOARDSPEED 523 | case KeyboardAutoRepeatRate: 524 | return QVariant(keyBoardAutoRepeatRateMS()); 525 | #endif 526 | case QPlatformIntegration::ShowIsMaximized: 527 | case QPlatformIntegration::StartDragTime: 528 | case QPlatformIntegration::StartDragDistance: 529 | case QPlatformIntegration::KeyboardInputInterval: 530 | case QPlatformIntegration::ShowIsFullScreen: 531 | case QPlatformIntegration::PasswordMaskDelay: 532 | case QPlatformIntegration::StartDragVelocity: 533 | break; // Not implemented 534 | case QPlatformIntegration::FontSmoothingGamma: 535 | return QVariant(QWindowsFontDatabase::fontSmoothingGamma()); 536 | case QPlatformIntegration::MouseDoubleClickInterval: 537 | if (const UINT ms = GetDoubleClickTime()) 538 | return QVariant(int(ms)); 539 | break; 540 | case QPlatformIntegration::UseRtlExtensions: 541 | return QVariant(d->m_context.useRTLExtensions()); 542 | default: 543 | break; 544 | } 545 | return QPlatformIntegration::styleHint(hint); 546 | } 547 | 548 | QPlatformKeyMapper *QWindowsIntegration::keyMapper() const 549 | { 550 | return d->m_context.keyMapper(); 551 | } 552 | 553 | #if QT_CONFIG(clipboard) 554 | QPlatformClipboard * QWindowsIntegration::clipboard() const 555 | { 556 | return &d->m_clipboard; 557 | } 558 | # if QT_CONFIG(draganddrop) 559 | QPlatformDrag *QWindowsIntegration::drag() const 560 | { 561 | return &d->m_drag; 562 | } 563 | # endif // QT_CONFIG(draganddrop) 564 | #endif // !QT_NO_CLIPBOARD 565 | 566 | QPlatformInputContext * QWindowsIntegration::inputContext() const 567 | { 568 | return d->m_inputContext.data(); 569 | } 570 | 571 | #if QT_CONFIG(accessibility) 572 | QPlatformAccessibility *QWindowsIntegration::accessibility() const 573 | { 574 | return &d->m_accessibility; 575 | } 576 | #endif 577 | 578 | unsigned QWindowsIntegration::options() const 579 | { 580 | return d->m_options; 581 | } 582 | 583 | #if QT_CONFIG(sessionmanager) 584 | QPlatformSessionManager *QWindowsIntegration::createPlatformSessionManager(const QString &id, const QString &key) const 585 | { 586 | return new QWindowsSessionManager(id, key); 587 | } 588 | #endif 589 | 590 | QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const 591 | { 592 | return new QWindowsGuiEventDispatcher; 593 | } 594 | 595 | QStringList QWindowsIntegration::themeNames() const 596 | { 597 | return QStringList(QLatin1StringView(QWindowsTheme::name)); 598 | } 599 | 600 | QPlatformTheme *QWindowsIntegration::createPlatformTheme(const QString &name) const 601 | { 602 | if (name == QLatin1StringView(QWindowsTheme::name)) 603 | return new QWindowsTheme; 604 | return QPlatformIntegration::createPlatformTheme(name); 605 | } 606 | 607 | QPlatformServices *QWindowsIntegration::services() const 608 | { 609 | if (d->m_services.isNull()) 610 | d->m_services.reset(new QWindowsServices); 611 | 612 | return d->m_services.data(); 613 | } 614 | 615 | void QWindowsIntegration::beep() const 616 | { 617 | MessageBeep(MB_OK); // For QApplication 618 | } 619 | 620 | void QWindowsIntegration::setApplicationBadge(qint64 number) 621 | { 622 | // Clamp to positive numbers, as the Windows API doesn't support negative numbers 623 | number = qMax(0, number); 624 | 625 | // Persist, so we can re-apply it on setting changes and Explorer restart 626 | m_applicationBadgeNumber = number; 627 | 628 | static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; 629 | 630 | #if QT_CONFIG(cpp_winrt) 631 | // We prefer the native BadgeUpdater API, that allows us to set a number directly, 632 | // but it requires that the application has a package identity, and also doesn't 633 | // seem to work in all cases on < Windows 11. 634 | QT_TRY { 635 | if (isWindows11 && qt_win_hasPackageIdentity()) { 636 | using namespace winrt::Windows::UI::Notifications; 637 | auto badgeXml = BadgeUpdateManager::GetTemplateContent(BadgeTemplateType::BadgeNumber); 638 | badgeXml.SelectSingleNode(L"//badge/@value").NodeValue(winrt::box_value(winrt::to_hstring(number))); 639 | BadgeUpdateManager::CreateBadgeUpdaterForApplication().Update(BadgeNotification(badgeXml)); 640 | return; 641 | } 642 | } QT_CATCH(...) { 643 | // fall back to win32 implementation 644 | } 645 | #endif 646 | 647 | // Fallback for non-packaged apps, Windows 10, or Qt builds without WinRT/C++ support 648 | 649 | if (!number) { 650 | // Clear badge 651 | setApplicationBadge(QImage()); 652 | return; 653 | } 654 | 655 | const bool isDarkMode = QWindowsTheme::instance()->colorScheme() 656 | == Qt::ColorScheme::Dark; 657 | 658 | QColor badgeColor; 659 | QColor textColor; 660 | 661 | #if QT_CONFIG(cpp_winrt) 662 | if (isWindows11) { 663 | // Match colors used by BadgeUpdater 664 | static const auto fromUIColor = [](winrt::Windows::UI::Color &&color) { 665 | return QColor(color.R, color.G, color.B, color.A); 666 | }; 667 | using namespace winrt::Windows::UI::ViewManagement; 668 | const auto settings = UISettings(); 669 | badgeColor = fromUIColor(settings.GetColorValue(isDarkMode ? 670 | UIColorType::AccentLight2 : UIColorType::Accent)); 671 | textColor = fromUIColor(settings.GetColorValue(UIColorType::Background)); 672 | } 673 | #endif 674 | 675 | if (!badgeColor.isValid()) { 676 | // Fall back to basic badge colors, based on Windows 10 look 677 | badgeColor = isDarkMode ? Qt::black : QColor(220, 220, 220); 678 | badgeColor.setAlphaF(0.5f); 679 | textColor = isDarkMode ? Qt::white : Qt::black; 680 | } 681 | 682 | const auto devicePixelRatio = qApp->devicePixelRatio(); 683 | 684 | static const QSize iconBaseSize(16, 16); 685 | QImage image(iconBaseSize * devicePixelRatio, 686 | QImage::Format_ARGB32_Premultiplied); 687 | image.fill(Qt::transparent); 688 | 689 | QPainter painter(&image); 690 | 691 | QRect badgeRect = image.rect(); 692 | QPen badgeBorderPen = Qt::NoPen; 693 | if (!isWindows11) { 694 | QColor badgeBorderColor = textColor; 695 | badgeBorderColor.setAlphaF(0.5f); 696 | badgeBorderPen = badgeBorderColor; 697 | badgeRect.adjust(1, 1, -1, -1); 698 | } 699 | painter.setBrush(badgeColor); 700 | painter.setPen(badgeBorderPen); 701 | painter.setRenderHint(QPainter::Antialiasing); 702 | painter.drawEllipse(badgeRect); 703 | 704 | auto pixelSize = qCeil(10.5 * devicePixelRatio); 705 | // Unlike the BadgeUpdater API we're limited by a square 706 | // badge, so adjust the font size when above two digits. 707 | const bool textOverflow = number > 99; 708 | if (textOverflow) 709 | pixelSize *= 0.8; 710 | 711 | QFont font = painter.font(); 712 | font.setPixelSize(pixelSize); 713 | font.setWeight(isWindows11 ? QFont::Medium : QFont::DemiBold); 714 | painter.setFont(font); 715 | 716 | painter.setRenderHint(QPainter::TextAntialiasing, devicePixelRatio > 1); 717 | painter.setPen(textColor); 718 | 719 | auto text = textOverflow ? u"99+"_s : QString::number(number); 720 | painter.translate(textOverflow ? 1 : 0, textOverflow ? 0 : -1); 721 | painter.drawText(image.rect(), Qt::AlignCenter, text); 722 | 723 | painter.end(); 724 | 725 | setApplicationBadge(image); 726 | } 727 | 728 | void QWindowsIntegration::setApplicationBadge(const QImage &image) 729 | { 730 | QComHelper comHelper; 731 | 732 | using Microsoft::WRL::ComPtr; 733 | 734 | ComPtr taskbarList; 735 | CoCreateInstance(CLSID_TaskbarList, nullptr, 736 | CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&taskbarList)); 737 | if (!taskbarList) { 738 | // There may not be any windows with a task bar button yet, 739 | // in which case we'll apply the badge once a window with 740 | // a button has been created. 741 | return; 742 | } 743 | 744 | const auto hIcon = image.toHICON(); 745 | 746 | // Apply the icon to all top level windows, since the badge is 747 | // set on an application level. If one of the windows go away 748 | // the other windows will take over in showing the badge. 749 | const auto topLevelWindows = QGuiApplication::topLevelWindows(); 750 | for (auto *topLevelWindow : topLevelWindows) { 751 | if (!topLevelWindow->handle()) 752 | continue; 753 | auto hwnd = reinterpret_cast(topLevelWindow->winId()); 754 | taskbarList->SetOverlayIcon(hwnd, hIcon, L""); 755 | } 756 | 757 | DestroyIcon(hIcon); 758 | 759 | // FIXME: Update icon when the application scale factor changes. 760 | // Doing so in response to screen DPI changes is too soon, as the 761 | // task bar is not yet ready for an updated icon, and will just 762 | // result in a blurred icon even if our icon is high-DPI. 763 | } 764 | 765 | void QWindowsIntegration::updateApplicationBadge() 766 | { 767 | // The system color settings have changed, or we are reacting 768 | // to a task bar button being created for the fist time or after 769 | // Explorer had crashed and re-started. In any case, re-apply the 770 | // badge so that everything is up to date. 771 | if (m_applicationBadgeNumber) 772 | setApplicationBadge(m_applicationBadgeNumber); 773 | } 774 | 775 | #if QT_CONFIG(vulkan) 776 | QPlatformVulkanInstance *QWindowsIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const 777 | { 778 | return new QWindowsVulkanInstance(instance); 779 | } 780 | #endif 781 | 782 | QT_END_NAMESPACE 783 | -------------------------------------------------------------------------------- /qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include 5 | #include "qwindowsdrag.h" 6 | #include "qwindowscontext.h" 7 | #include "qwindowsscreen.h" 8 | #if QT_CONFIG(clipboard) 9 | # include "qwindowsclipboard.h" 10 | #endif 11 | #include "qwindowsintegration.h" 12 | #include "qwindowsdropdataobject.h" 13 | #include "qwindowswindow.h" 14 | #include "qwindowspointerhandler.h" 15 | #include "qwindowscursor.h" 16 | #include "qwindowskeymapper.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include "vxkex.h" 37 | 38 | QT_BEGIN_NAMESPACE 39 | 40 | /*! 41 | \class QWindowsDragCursorWindow 42 | \brief A toplevel window showing the drag icon in case of touch drag. 43 | 44 | \sa QWindowsOleDropSource 45 | \internal 46 | */ 47 | 48 | class QWindowsDragCursorWindow : public QRasterWindow 49 | { 50 | public: 51 | explicit QWindowsDragCursorWindow(QWindow *parent = nullptr); 52 | 53 | void setPixmap(const QPixmap &p); 54 | 55 | protected: 56 | void paintEvent(QPaintEvent *) override 57 | { 58 | QPainter painter(this); 59 | painter.drawPixmap(0, 0, m_pixmap); 60 | } 61 | 62 | private: 63 | QPixmap m_pixmap; 64 | }; 65 | 66 | QWindowsDragCursorWindow::QWindowsDragCursorWindow(QWindow *parent) 67 | : QRasterWindow(parent) 68 | { 69 | QSurfaceFormat windowFormat = format(); 70 | windowFormat.setAlphaBufferSize(8); 71 | setFormat(windowFormat); 72 | setObjectName(QStringLiteral("QWindowsDragCursorWindow")); 73 | setFlags(Qt::Popup | Qt::NoDropShadowWindowHint 74 | | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint 75 | | Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput); 76 | } 77 | 78 | void QWindowsDragCursorWindow::setPixmap(const QPixmap &p) 79 | { 80 | if (p.cacheKey() == m_pixmap.cacheKey()) 81 | return; 82 | const QSize oldSize = m_pixmap.size(); 83 | QSize newSize = p.size(); 84 | qCDebug(lcQpaMime) << __FUNCTION__ << p.cacheKey() << newSize; 85 | m_pixmap = p; 86 | if (oldSize != newSize) { 87 | const qreal pixDevicePixelRatio = p.devicePixelRatio(); 88 | if (pixDevicePixelRatio > 1.0 && qFuzzyCompare(pixDevicePixelRatio, devicePixelRatio())) 89 | newSize /= qRound(pixDevicePixelRatio); 90 | resize(newSize); 91 | } 92 | if (isVisible()) 93 | update(); 94 | } 95 | 96 | /*! 97 | \class QWindowsDropMimeData 98 | \brief Special mime data class for data retrieval from Drag operations. 99 | 100 | Implementation of QWindowsInternalMimeDataBase which retrieves the 101 | current drop data object from QWindowsDrag. 102 | 103 | \sa QWindowsDrag 104 | \internal 105 | */ 106 | 107 | IDataObject *QWindowsDropMimeData::retrieveDataObject() const 108 | { 109 | return QWindowsDrag::instance()->dropDataObject(); 110 | } 111 | 112 | static inline Qt::DropActions translateToQDragDropActions(DWORD pdwEffects) 113 | { 114 | Qt::DropActions actions = Qt::IgnoreAction; 115 | if (pdwEffects & DROPEFFECT_LINK) 116 | actions |= Qt::LinkAction; 117 | if (pdwEffects & DROPEFFECT_COPY) 118 | actions |= Qt::CopyAction; 119 | if (pdwEffects & DROPEFFECT_MOVE) 120 | actions |= Qt::MoveAction; 121 | return actions; 122 | } 123 | 124 | static inline Qt::DropAction translateToQDragDropAction(DWORD pdwEffect) 125 | { 126 | if (pdwEffect & DROPEFFECT_LINK) 127 | return Qt::LinkAction; 128 | if (pdwEffect & DROPEFFECT_COPY) 129 | return Qt::CopyAction; 130 | if (pdwEffect & DROPEFFECT_MOVE) 131 | return Qt::MoveAction; 132 | return Qt::IgnoreAction; 133 | } 134 | 135 | static inline DWORD translateToWinDragEffects(Qt::DropActions action) 136 | { 137 | DWORD effect = DROPEFFECT_NONE; 138 | if (action & Qt::LinkAction) 139 | effect |= DROPEFFECT_LINK; 140 | if (action & Qt::CopyAction) 141 | effect |= DROPEFFECT_COPY; 142 | if (action & Qt::MoveAction) 143 | effect |= DROPEFFECT_MOVE; 144 | return effect; 145 | } 146 | 147 | static inline Qt::KeyboardModifiers toQtKeyboardModifiers(DWORD keyState) 148 | { 149 | Qt::KeyboardModifiers modifiers = Qt::NoModifier; 150 | 151 | if (keyState & MK_SHIFT) 152 | modifiers |= Qt::ShiftModifier; 153 | if (keyState & MK_CONTROL) 154 | modifiers |= Qt::ControlModifier; 155 | if (keyState & MK_ALT) 156 | modifiers |= Qt::AltModifier; 157 | 158 | return modifiers; 159 | } 160 | 161 | static Qt::KeyboardModifiers lastModifiers = Qt::NoModifier; 162 | static Qt::MouseButtons lastButtons = Qt::NoButton; 163 | 164 | /*! 165 | \class QWindowsOleDropSource 166 | \brief Implementation of IDropSource 167 | 168 | Used for drag operations. 169 | 170 | \sa QWindowsDrag 171 | \internal 172 | */ 173 | 174 | class QWindowsOleDropSource : public QComObject 175 | { 176 | public: 177 | enum Mode { 178 | MouseDrag, 179 | TouchDrag // Mouse cursor suppressed, use window as cursor. 180 | }; 181 | 182 | explicit QWindowsOleDropSource(QWindowsDrag *drag); 183 | ~QWindowsOleDropSource() override; 184 | 185 | void createCursors(); 186 | 187 | // IDropSource methods 188 | STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState) override; 189 | STDMETHOD(GiveFeedback)(DWORD dwEffect) override; 190 | 191 | private: 192 | struct CursorEntry { 193 | CursorEntry() : cacheKey(0) {} 194 | CursorEntry(const QPixmap &p, qint64 cK, const CursorHandlePtr &c, const QPoint &h) : 195 | pixmap(p), cacheKey(cK), cursor(c), hotSpot(h) {} 196 | 197 | QPixmap pixmap; 198 | qint64 cacheKey; // Cache key of cursor 199 | CursorHandlePtr cursor; 200 | QPoint hotSpot; 201 | }; 202 | 203 | typedef QMap ActionCursorMap; 204 | 205 | Mode m_mode; 206 | QWindowsDrag *m_drag; 207 | QPointer m_windowUnderMouse; 208 | Qt::MouseButtons m_currentButtons; 209 | ActionCursorMap m_cursors; 210 | QWindowsDragCursorWindow *m_touchDragWindow; 211 | 212 | #ifndef QT_NO_DEBUG_STREAM 213 | friend QDebug operator<<(QDebug, const QWindowsOleDropSource::CursorEntry &); 214 | #endif 215 | }; 216 | 217 | QWindowsOleDropSource::QWindowsOleDropSource(QWindowsDrag *drag) 218 | : m_mode(QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed ? MouseDrag : TouchDrag) 219 | , m_drag(drag) 220 | , m_windowUnderMouse(QWindowsContext::instance()->windowUnderMouse()) 221 | , m_currentButtons(Qt::NoButton) 222 | , m_touchDragWindow(nullptr) 223 | { 224 | qCDebug(lcQpaMime) << __FUNCTION__ << m_mode; 225 | } 226 | 227 | QWindowsOleDropSource::~QWindowsOleDropSource() 228 | { 229 | m_cursors.clear(); 230 | delete m_touchDragWindow; 231 | qCDebug(lcQpaMime) << __FUNCTION__; 232 | } 233 | 234 | #ifndef QT_NO_DEBUG_STREAM 235 | QDebug operator<<(QDebug d, const QWindowsOleDropSource::CursorEntry &e) 236 | { 237 | d << "CursorEntry:" << e.pixmap.size() << '#' << e.cacheKey 238 | << "HCURSOR" << e.cursor->handle() << "hotspot:" << e.hotSpot; 239 | return d; 240 | } 241 | #endif // !QT_NO_DEBUG_STREAM 242 | 243 | /*! 244 | \brief Blend custom pixmap with cursors. 245 | */ 246 | 247 | void QWindowsOleDropSource::createCursors() 248 | { 249 | const QDrag *drag = m_drag->currentDrag(); 250 | const QPixmap pixmap = drag->pixmap(); 251 | const bool hasPixmap = !pixmap.isNull(); 252 | 253 | // Find screen for drag. Could be obtained from QDrag::source(), but that might be a QWidget. 254 | const QPlatformScreen *platformScreen = QWindowsContext::instance()->screenManager().screenAtDp(QWindowsCursor::mousePosition()); 255 | if (!platformScreen) { 256 | if (const QScreen *primaryScreen = QGuiApplication::primaryScreen()) 257 | platformScreen = primaryScreen->handle(); 258 | } 259 | QPlatformCursor *platformCursor = nullptr; 260 | if (platformScreen) 261 | platformCursor = platformScreen->cursor(); 262 | 263 | if (GetSystemMetrics (SM_REMOTESESSION) != 0) { 264 | /* Workaround for RDP issues with large cursors. 265 | * Touch drag window seems to work just fine... 266 | * 96 pixel is a 'large' mouse cursor, according to RDP spec */ 267 | const int rdpLargeCursor = qRound(qreal(96) / QHighDpiScaling::factor(platformScreen)); 268 | if (pixmap.width() > rdpLargeCursor || pixmap.height() > rdpLargeCursor) 269 | m_mode = TouchDrag; 270 | } 271 | 272 | qreal pixmapScaleFactor = 1; 273 | qreal hotSpotScaleFactor = 1; 274 | if (m_mode != TouchDrag) { // Touch drag: pixmap is shown in a separate QWindow, which will be scaled.) 275 | hotSpotScaleFactor = QHighDpiScaling::factor(platformScreen); 276 | pixmapScaleFactor = hotSpotScaleFactor / pixmap.devicePixelRatio(); 277 | } 278 | QPixmap scaledPixmap = (!hasPixmap || qFuzzyCompare(pixmapScaleFactor, 1.0)) 279 | ? pixmap 280 | : pixmap.scaled((QSizeF(pixmap.size()) * pixmapScaleFactor).toSize(), 281 | Qt::KeepAspectRatio, Qt::SmoothTransformation); 282 | scaledPixmap.setDevicePixelRatio(1); 283 | 284 | Qt::DropAction actions[] = { Qt::MoveAction, Qt::CopyAction, Qt::LinkAction, Qt::IgnoreAction }; 285 | int actionCount = int(sizeof(actions) / sizeof(actions[0])); 286 | if (!hasPixmap) 287 | --actionCount; // No Qt::IgnoreAction unless pixmap 288 | const QPoint hotSpot = qFuzzyCompare(hotSpotScaleFactor, 1.0) 289 | ? drag->hotSpot() 290 | : (QPointF(drag->hotSpot()) * hotSpotScaleFactor).toPoint(); 291 | for (int cnum = 0; cnum < actionCount; ++cnum) { 292 | const Qt::DropAction action = actions[cnum]; 293 | QPixmap cursorPixmap = drag->dragCursor(action); 294 | if (cursorPixmap.isNull() && platformCursor) 295 | cursorPixmap = static_cast(platformCursor)->dragDefaultCursor(action); 296 | const qint64 cacheKey = cursorPixmap.cacheKey(); 297 | const auto it = m_cursors.find(action); 298 | if (it != m_cursors.end() && it.value().cacheKey == cacheKey) 299 | continue; 300 | if (cursorPixmap.isNull()) { 301 | qWarning("%s: Unable to obtain drag cursor for %d.", __FUNCTION__, action); 302 | continue; 303 | } 304 | 305 | QPoint newHotSpot(0, 0); 306 | QPixmap newPixmap = cursorPixmap; 307 | 308 | if (hasPixmap) { 309 | const int x1 = qMin(-hotSpot.x(), 0); 310 | const int x2 = qMax(scaledPixmap.width() - hotSpot.x(), cursorPixmap.width()); 311 | const int y1 = qMin(-hotSpot.y(), 0); 312 | const int y2 = qMax(scaledPixmap.height() - hotSpot.y(), cursorPixmap.height()); 313 | QPixmap newCursor(x2 - x1 + 1, y2 - y1 + 1); 314 | newCursor.fill(Qt::transparent); 315 | QPainter p(&newCursor); 316 | const QPoint pmDest = QPoint(qMax(0, -hotSpot.x()), qMax(0, -hotSpot.y())); 317 | p.drawPixmap(pmDest, scaledPixmap); 318 | p.drawPixmap(qMax(0, hotSpot.x()),qMax(0, hotSpot.y()), cursorPixmap); 319 | newPixmap = newCursor; 320 | newHotSpot = QPoint(qMax(0, hotSpot.x()), qMax(0, hotSpot.y())); 321 | } 322 | 323 | if (const HCURSOR sysCursor = QWindowsCursor::createPixmapCursor(newPixmap, newHotSpot)) { 324 | const CursorEntry entry(newPixmap, cacheKey, CursorHandlePtr(new CursorHandle(sysCursor)), newHotSpot); 325 | if (it == m_cursors.end()) 326 | m_cursors.insert(action, entry); 327 | else 328 | it.value() = entry; 329 | } 330 | } 331 | #ifndef QT_NO_DEBUG_OUTPUT 332 | if (lcQpaMime().isDebugEnabled()) 333 | qCDebug(lcQpaMime) << __FUNCTION__ << "pixmap" << pixmap.size() << m_cursors.size() << "cursors:\n" << m_cursors; 334 | #endif // !QT_NO_DEBUG_OUTPUT 335 | } 336 | 337 | /*! 338 | \brief Check for cancel. 339 | */ 340 | 341 | QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP 342 | QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) 343 | { 344 | // In some rare cases, when a mouse button is released but the mouse is static, 345 | // grfKeyState will not be updated with these released buttons until the mouse 346 | // is moved. So we use the async key state given by queryMouseButtons() instead. 347 | Qt::MouseButtons buttons = QWindowsPointerHandler::queryMouseButtons(); 348 | 349 | SCODE result = S_OK; 350 | if (fEscapePressed || QWindowsDrag::isCanceled()) { 351 | result = DRAGDROP_S_CANCEL; 352 | buttons = Qt::NoButton; 353 | } else { 354 | if (buttons && !m_currentButtons) { 355 | m_currentButtons = buttons; 356 | } else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation. 357 | result = DRAGDROP_S_DROP; 358 | } 359 | } 360 | 361 | switch (result) { 362 | case DRAGDROP_S_DROP: 363 | case DRAGDROP_S_CANCEL: 364 | if (!m_windowUnderMouse.isNull() && m_mode != TouchDrag && fEscapePressed == FALSE 365 | && buttons != lastButtons) { 366 | // QTBUG 66447: Synthesize a mouse release to the window under mouse at 367 | // start of the DnD operation as Windows does not send any. 368 | const QPoint globalPos = QWindowsCursor::mousePosition(); 369 | const QPoint localPos = m_windowUnderMouse->handle()->mapFromGlobal(globalPos); 370 | QWindowSystemInterface::handleMouseEvent(m_windowUnderMouse.data(), 371 | QPointF(localPos), QPointF(globalPos), 372 | QWindowsPointerHandler::queryMouseButtons(), 373 | Qt::LeftButton, QEvent::MouseButtonRelease); 374 | } 375 | m_currentButtons = Qt::NoButton; 376 | break; 377 | 378 | default: 379 | QGuiApplication::processEvents(); 380 | break; 381 | } 382 | 383 | if (QWindowsContext::verbose > 1 || result != S_OK) { 384 | qCDebug(lcQpaMime) << __FUNCTION__ << "fEscapePressed=" << fEscapePressed 385 | << "grfKeyState=" << grfKeyState << "buttons" << m_currentButtons 386 | << "returns 0x" << Qt::hex << int(result) << Qt::dec; 387 | } 388 | return ResultFromScode(result); 389 | } 390 | 391 | /*! 392 | \brief Give feedback: Change cursor according to action. 393 | */ 394 | 395 | QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP 396 | QWindowsOleDropSource::GiveFeedback(DWORD dwEffect) 397 | { 398 | const Qt::DropAction action = translateToQDragDropAction(dwEffect); 399 | m_drag->updateAction(action); 400 | 401 | const qint64 currentCacheKey = m_drag->currentDrag()->dragCursor(action).cacheKey(); 402 | auto it = m_cursors.constFind(action); 403 | // If a custom drag cursor is set, check its cache key to detect changes. 404 | if (it == m_cursors.constEnd() || (currentCacheKey && currentCacheKey != it.value().cacheKey)) { 405 | createCursors(); 406 | it = m_cursors.constFind(action); 407 | } 408 | 409 | if (it != m_cursors.constEnd()) { 410 | const CursorEntry &e = it.value(); 411 | switch (m_mode) { 412 | case MouseDrag: 413 | SetCursor(e.cursor->handle()); 414 | break; 415 | case TouchDrag: 416 | // "Touch drag" with an unsuppressed cursor may happen with RDP (see createCursors()) 417 | if (QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed) 418 | SetCursor(nullptr); 419 | if (!m_touchDragWindow) 420 | m_touchDragWindow = new QWindowsDragCursorWindow; 421 | m_touchDragWindow->setPixmap(e.pixmap); 422 | m_touchDragWindow->setFramePosition(QCursor::pos() - e.hotSpot); 423 | if (!m_touchDragWindow->isVisible()) 424 | m_touchDragWindow->show(); 425 | break; 426 | } 427 | return ResultFromScode(S_OK); 428 | } 429 | 430 | return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS); 431 | } 432 | 433 | /*! 434 | \class QWindowsOleDropTarget 435 | \brief Implementation of IDropTarget 436 | 437 | To be registered for each window. Currently, drop sites 438 | are enabled for top levels. The child window handling 439 | (sending DragEnter/Leave, etc) is handled in here. 440 | 441 | \sa QWindowsDrag 442 | \internal 443 | */ 444 | 445 | QWindowsOleDropTarget::QWindowsOleDropTarget(QWindow *w) : m_window(w) 446 | { 447 | qCDebug(lcQpaMime) << __FUNCTION__ << this << w; 448 | } 449 | 450 | QWindowsOleDropTarget::~QWindowsOleDropTarget() 451 | { 452 | qCDebug(lcQpaMime) << __FUNCTION__ << this; 453 | } 454 | 455 | void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState, 456 | const QPoint &point, LPDWORD pdwEffect) 457 | { 458 | Q_ASSERT(window); 459 | m_lastPoint = point; 460 | m_lastKeyState = grfKeyState; 461 | 462 | QWindowsDrag *windowsDrag = QWindowsDrag::instance(); 463 | const Qt::DropActions actions = translateToQDragDropActions(*pdwEffect); 464 | 465 | lastModifiers = toQtKeyboardModifiers(grfKeyState); 466 | lastButtons = QWindowsPointerHandler::queryMouseButtons(); 467 | 468 | const QPlatformDragQtResponse response = 469 | QWindowSystemInterface::handleDrag(window, windowsDrag->dropData(), 470 | m_lastPoint, actions, 471 | lastButtons, lastModifiers); 472 | 473 | m_answerRect = response.answerRect(); 474 | const Qt::DropAction action = response.acceptedAction(); 475 | if (response.isAccepted()) { 476 | m_chosenEffect = translateToWinDragEffects(action); 477 | } else { 478 | m_chosenEffect = DROPEFFECT_NONE; 479 | } 480 | *pdwEffect = m_chosenEffect; 481 | qCDebug(lcQpaMime) << __FUNCTION__ << m_window 482 | << windowsDrag->dropData() << " supported actions=" << actions 483 | << " mods=" << lastModifiers << " mouse=" << lastButtons 484 | << " accepted: " << response.isAccepted() << action 485 | << m_answerRect << " effect" << *pdwEffect; 486 | } 487 | 488 | QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP 489 | QWindowsOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState, 490 | POINTL pt, LPDWORD pdwEffect) 491 | { 492 | if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) 493 | dh->DragEnter(reinterpret_cast(m_window->winId()), pDataObj, reinterpret_cast(&pt), *pdwEffect); 494 | 495 | qCDebug(lcQpaMime) << __FUNCTION__ << "widget=" << m_window << " key=" << grfKeyState 496 | << "pt=" << pt.x << pt.y; 497 | 498 | QWindowsDrag::instance()->setDropDataObject(pDataObj); 499 | pDataObj->AddRef(); 500 | const QPoint point = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y)); 501 | handleDrag(m_window, grfKeyState, point, pdwEffect); 502 | return NOERROR; 503 | } 504 | 505 | QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP 506 | QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) 507 | { 508 | if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) 509 | dh->DragOver(reinterpret_cast(&pt), *pdwEffect); 510 | 511 | qCDebug(lcQpaMime) << __FUNCTION__ << "m_window" << m_window << "key=" << grfKeyState 512 | << "pt=" << pt.x << pt.y; 513 | const QPoint tmpPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y)); 514 | // see if we should compress this event 515 | if ((tmpPoint == m_lastPoint || m_answerRect.contains(tmpPoint)) 516 | && m_lastKeyState == grfKeyState) { 517 | *pdwEffect = m_chosenEffect; 518 | qCDebug(lcQpaMime) << __FUNCTION__ << "compressed event"; 519 | return NOERROR; 520 | } 521 | 522 | handleDrag(m_window, grfKeyState, tmpPoint, pdwEffect); 523 | return NOERROR; 524 | } 525 | 526 | QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP 527 | QWindowsOleDropTarget::DragLeave() 528 | { 529 | if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) 530 | dh->DragLeave(); 531 | 532 | qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window; 533 | 534 | const auto *keyMapper = QWindowsContext::instance()->keyMapper(); 535 | lastModifiers = keyMapper->queryKeyboardModifiers(); 536 | lastButtons = QWindowsPointerHandler::queryMouseButtons(); 537 | 538 | QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction, 539 | Qt::NoButton, Qt::NoModifier); 540 | 541 | if (!QDragManager::self()->source()) 542 | m_lastKeyState = 0; 543 | QWindowsDrag::instance()->releaseDropDataObject(); 544 | 545 | return NOERROR; 546 | } 547 | 548 | #define KEY_STATE_BUTTON_MASK (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) 549 | 550 | QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP 551 | QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState, 552 | POINTL pt, LPDWORD pdwEffect) 553 | { 554 | if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper()) 555 | dh->Drop(pDataObj, reinterpret_cast(&pt), *pdwEffect); 556 | 557 | qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window 558 | << "keys=" << grfKeyState << "pt=" << pt.x << ',' << pt.y; 559 | 560 | m_lastPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y)); 561 | 562 | QWindowsDrag *windowsDrag = QWindowsDrag::instance(); 563 | 564 | lastModifiers = toQtKeyboardModifiers(grfKeyState); 565 | lastButtons = QWindowsPointerHandler::queryMouseButtons(); 566 | 567 | const QPlatformDropQtResponse response = 568 | QWindowSystemInterface::handleDrop(m_window, windowsDrag->dropData(), 569 | m_lastPoint, 570 | translateToQDragDropActions(*pdwEffect), 571 | lastButtons, 572 | lastModifiers); 573 | 574 | m_lastKeyState = grfKeyState; 575 | 576 | if (response.isAccepted()) { 577 | const Qt::DropAction action = response.acceptedAction(); 578 | if (action == Qt::MoveAction || action == Qt::TargetMoveAction) { 579 | if (action == Qt::MoveAction) 580 | m_chosenEffect = DROPEFFECT_MOVE; 581 | else 582 | m_chosenEffect = DROPEFFECT_COPY; 583 | HGLOBAL hData = GlobalAlloc(0, sizeof(DWORD)); 584 | if (hData) { 585 | auto *moveEffect = reinterpret_cast(GlobalLock(hData)); 586 | *moveEffect = DROPEFFECT_MOVE; 587 | GlobalUnlock(hData); 588 | STGMEDIUM medium; 589 | memset(&medium, 0, sizeof(STGMEDIUM)); 590 | medium.tymed = TYMED_HGLOBAL; 591 | medium.hGlobal = hData; 592 | FORMATETC format; 593 | format.cfFormat = CLIPFORMAT(RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT)); 594 | format.tymed = TYMED_HGLOBAL; 595 | format.ptd = nullptr; 596 | format.dwAspect = 1; 597 | format.lindex = -1; 598 | windowsDrag->dropDataObject()->SetData(&format, &medium, true); 599 | } 600 | } else { 601 | m_chosenEffect = translateToWinDragEffects(action); 602 | } 603 | } else { 604 | m_chosenEffect = DROPEFFECT_NONE; 605 | } 606 | *pdwEffect = m_chosenEffect; 607 | 608 | windowsDrag->releaseDropDataObject(); 609 | return NOERROR; 610 | } 611 | 612 | 613 | /*! 614 | \class QWindowsDrag 615 | \brief Windows drag implementation. 616 | \internal 617 | */ 618 | 619 | bool QWindowsDrag::m_canceled = false; 620 | 621 | QWindowsDrag::QWindowsDrag() = default; 622 | 623 | QWindowsDrag::~QWindowsDrag() 624 | { 625 | if (m_cachedDropTargetHelper) 626 | m_cachedDropTargetHelper->Release(); 627 | } 628 | 629 | /*! 630 | \brief Return data for a drop in process. If it stems from a current drag, use a shortcut. 631 | */ 632 | 633 | QMimeData *QWindowsDrag::dropData() 634 | { 635 | if (const QDrag *drag = currentDrag()) 636 | return drag->mimeData(); 637 | return &m_dropData; 638 | } 639 | 640 | /*! 641 | \brief May be used to handle extended cursors functionality for drags from outside the app. 642 | */ 643 | IDropTargetHelper* QWindowsDrag::dropHelper() { 644 | if (!m_cachedDropTargetHelper) { 645 | CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, 646 | IID_IDropTargetHelper, 647 | reinterpret_cast(&m_cachedDropTargetHelper)); 648 | } 649 | return m_cachedDropTargetHelper; 650 | } 651 | 652 | // Workaround for DoDragDrop() not working with touch/pen input, causing DnD to hang until the mouse is moved. 653 | // We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop() 654 | static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) 655 | { 656 | QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse(); 657 | const bool hasMouseCapture = underMouse && static_cast(underMouse->handle())->hasMouseCapture(); 658 | const HWND hwnd = hasMouseCapture ? reinterpret_cast(underMouse->winId()) : ::GetFocus(); 659 | bool starting = false; 660 | 661 | for (;;) { 662 | MSG msg{}; 663 | if (::GetMessage(&msg, hwnd, 0, 0) > 0) { 664 | 665 | if (msg.message == WM_MOUSEMOVE) { 666 | 667 | // Only consider the first simulated event, or actual mouse messages. 668 | if (!starting && (msg.wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0) 669 | return E_FAIL; 670 | 671 | return ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect); 672 | } 673 | 674 | if (msg.message == WM_POINTERUPDATE) { 675 | 676 | const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam); 677 | 678 | POINTER_INFO pointerInfo{}; 679 | BOOL bResultPointerInfo = QWindowsContext::user32dll.getPointerInfo ? 680 | QWindowsContext::user32dll.getPointerInfo(pointerId, &pointerInfo) : 681 | vxkex::GetPointerInfo(pointerId, &pointerInfo); 682 | 683 | if (!bResultPointerInfo) 684 | return E_FAIL; 685 | 686 | if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) { 687 | 688 | DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_MOVE; 689 | if (IS_POINTER_FIRSTBUTTON_WPARAM(msg.wParam)) 690 | flags |= MOUSEEVENTF_LEFTDOWN; 691 | if (IS_POINTER_SECONDBUTTON_WPARAM(msg.wParam)) 692 | flags |= MOUSEEVENTF_RIGHTDOWN; 693 | if (IS_POINTER_THIRDBUTTON_WPARAM(msg.wParam)) 694 | flags |= MOUSEEVENTF_MIDDLEDOWN; 695 | 696 | if (!starting) { 697 | POINT pt{}; 698 | if (::GetCursorPos(&pt)) { 699 | 700 | // Send mouse input that can generate a WM_MOUSEMOVE message. 701 | if ((flags & MOUSEEVENTF_LEFTDOWN || flags & MOUSEEVENTF_RIGHTDOWN || flags & MOUSEEVENTF_MIDDLEDOWN) 702 | && (pt.x != pointerInfo.ptPixelLocation.x || pt.y != pointerInfo.ptPixelLocation.y)) { 703 | 704 | const int origin_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN); 705 | const int origin_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN); 706 | const int virt_w = ::GetSystemMetrics(SM_CXVIRTUALSCREEN); 707 | const int virt_h = ::GetSystemMetrics(SM_CYVIRTUALSCREEN); 708 | const int virt_x = pointerInfo.ptPixelLocation.x - origin_x; 709 | const int virt_y = pointerInfo.ptPixelLocation.y - origin_y; 710 | 711 | INPUT input{}; 712 | input.type = INPUT_MOUSE; 713 | input.mi.dx = static_cast(virt_x * (65535.0 / virt_w)); 714 | input.mi.dy = static_cast(virt_y * (65535.0 / virt_h)); 715 | input.mi.dwFlags = flags; 716 | 717 | ::SendInput(1, &input, sizeof(input)); 718 | starting = true; 719 | } 720 | } 721 | } 722 | } 723 | } else { 724 | // Handle other messages. 725 | qWindowsWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); 726 | 727 | if (msg.message == WM_POINTERLEAVE) 728 | return E_FAIL; 729 | } 730 | } else { 731 | return E_FAIL; 732 | } 733 | } 734 | } 735 | 736 | Qt::DropAction QWindowsDrag::drag(QDrag *drag) 737 | { 738 | // TODO: Accessibility handling? 739 | QMimeData *dropData = drag->mimeData(); 740 | Qt::DropAction dragResult = Qt::IgnoreAction; 741 | 742 | DWORD resultEffect; 743 | QWindowsDrag::m_canceled = false; 744 | auto *windowDropSource = new QWindowsOleDropSource(this); 745 | windowDropSource->createCursors(); 746 | auto *dropDataObject = new QWindowsDropDataObject(dropData); 747 | const Qt::DropActions possibleActions = drag->supportedActions(); 748 | const DWORD allowedEffects = translateToWinDragEffects(possibleActions); 749 | qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x" 750 | << Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec; 751 | const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect); 752 | const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect(); 753 | if (r == DRAGDROP_S_DROP) { 754 | if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) { 755 | dragResult = Qt::TargetMoveAction; 756 | resultEffect = DROPEFFECT_MOVE; 757 | } else { 758 | dragResult = translateToQDragDropAction(resultEffect); 759 | } 760 | // Force it to be a copy if an unsupported operation occurred. 761 | // This indicates a bug in the drop target. 762 | if (resultEffect != DROPEFFECT_NONE && !(resultEffect & allowedEffects)) { 763 | qWarning("%s: Forcing Qt::CopyAction", __FUNCTION__); 764 | dragResult = Qt::CopyAction; 765 | } 766 | } 767 | // clean up 768 | dropDataObject->releaseQt(); 769 | dropDataObject->Release(); // Will delete obj if refcount becomes 0 770 | windowDropSource->Release(); // Will delete src if refcount becomes 0 771 | qCDebug(lcQpaMime) << '<' << __FUNCTION__ << Qt::hex << "allowedEffects=0x" << allowedEffects 772 | << "reportedPerformedEffect=0x" << reportedPerformedEffect 773 | << " resultEffect=0x" << resultEffect << "hr=0x" << int(r) << Qt::dec << "dropAction=" << dragResult; 774 | return dragResult; 775 | } 776 | 777 | QWindowsDrag *QWindowsDrag::instance() 778 | { 779 | return static_cast(QWindowsIntegration::instance()->drag()); 780 | } 781 | 782 | void QWindowsDrag::releaseDropDataObject() 783 | { 784 | qCDebug(lcQpaMime) << __FUNCTION__ << m_dropDataObject; 785 | if (m_dropDataObject) { 786 | m_dropDataObject->Release(); 787 | m_dropDataObject = nullptr; 788 | } 789 | } 790 | 791 | QT_END_NAMESPACE 792 | -------------------------------------------------------------------------------- /qtbase/src/corelib/kernel/qeventdispatcher_win.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // Copyright (C) 2016 Intel Corporation. 3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 4 | 5 | #include "qeventdispatcher_win_p.h" 6 | 7 | #include "qcoreapplication.h" 8 | #include 9 | #include "qoperatingsystemversion.h" 10 | #include "qset.h" 11 | #include "qsocketnotifier.h" 12 | #include "qvarlengtharray.h" 13 | 14 | #include "qelapsedtimer.h" 15 | #include "qcoreapplication_p.h" 16 | #include 17 | 18 | QT_BEGIN_NAMESPACE 19 | 20 | #ifndef TIME_KILL_SYNCHRONOUS 21 | # define TIME_KILL_SYNCHRONOUS 0x0100 22 | #endif 23 | 24 | #ifndef QS_RAWINPUT 25 | # define QS_RAWINPUT 0x0400 26 | #endif 27 | 28 | #ifndef WM_TOUCH 29 | # define WM_TOUCH 0x0240 30 | #endif 31 | #ifndef QT_NO_GESTURES 32 | #ifndef WM_GESTURE 33 | # define WM_GESTURE 0x0119 34 | #endif 35 | #ifndef WM_GESTURENOTIFY 36 | # define WM_GESTURENOTIFY 0x011A 37 | #endif 38 | #endif // QT_NO_GESTURES 39 | 40 | enum { 41 | WM_QT_SOCKETNOTIFIER = WM_USER, 42 | WM_QT_SENDPOSTEDEVENTS = WM_USER + 1, 43 | WM_QT_ACTIVATENOTIFIERS = WM_USER + 2 44 | }; 45 | 46 | enum { 47 | SendPostedEventsTimerId = ~1u 48 | }; 49 | 50 | class QEventDispatcherWin32Private; 51 | 52 | #if !defined(DWORD_PTR) && !defined(Q_OS_WIN64) 53 | #define DWORD_PTR DWORD 54 | #endif 55 | 56 | LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp); 57 | 58 | static quint64 qt_msectime() 59 | { 60 | using namespace std::chrono; 61 | auto t = duration_cast(steady_clock::now().time_since_epoch()); 62 | return t.count(); 63 | } 64 | 65 | QEventDispatcherWin32Private::QEventDispatcherWin32Private() 66 | : interrupt(false), internalHwnd(0), 67 | sendPostedEventsTimerId(0), wakeUps(0), 68 | activateNotifiersPosted(false) 69 | { 70 | } 71 | 72 | QEventDispatcherWin32Private::~QEventDispatcherWin32Private() 73 | { 74 | if (internalHwnd) 75 | DestroyWindow(internalHwnd); 76 | } 77 | 78 | // This function is called by a workerthread 79 | void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc(uint timerId, uint /*reserved*/, DWORD_PTR user, DWORD_PTR /*reserved*/, DWORD_PTR /*reserved*/) 80 | { 81 | if (!timerId) // sanity check 82 | return; 83 | auto t = reinterpret_cast(user); 84 | Q_ASSERT(t); 85 | QCoreApplication::postEvent(t->dispatcher, new QTimerEvent(t->timerId)); 86 | } 87 | 88 | LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp) 89 | { 90 | if (message == WM_NCCREATE) 91 | return true; 92 | 93 | MSG msg; 94 | msg.hwnd = hwnd; 95 | msg.message = message; 96 | msg.wParam = wp; 97 | msg.lParam = lp; 98 | QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(); 99 | qintptr result; 100 | if (!dispatcher) { 101 | if (message == WM_TIMER) 102 | KillTimer(hwnd, wp); 103 | return 0; 104 | } 105 | if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result)) 106 | return result; 107 | 108 | auto q = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); 109 | QEventDispatcherWin32Private *d = nullptr; 110 | if (q != nullptr) 111 | d = q->d_func(); 112 | 113 | switch (message) { 114 | case WM_QT_SOCKETNOTIFIER: { 115 | // socket notifier message 116 | int type = -1; 117 | switch (WSAGETSELECTEVENT(lp)) { 118 | case FD_READ: 119 | case FD_ACCEPT: 120 | type = 0; 121 | break; 122 | case FD_WRITE: 123 | case FD_CONNECT: 124 | type = 1; 125 | break; 126 | case FD_OOB: 127 | type = 2; 128 | break; 129 | case FD_CLOSE: 130 | type = 3; 131 | break; 132 | } 133 | if (type >= 0) { 134 | Q_ASSERT(d != nullptr); 135 | QSNDict *sn_vec[4] = { &d->sn_read, &d->sn_write, &d->sn_except, &d->sn_read }; 136 | QSNDict *dict = sn_vec[type]; 137 | 138 | QSockNot *sn = dict ? dict->value(qintptr(wp)) : 0; 139 | if (sn == nullptr) { 140 | d->postActivateSocketNotifiers(); 141 | } else { 142 | Q_ASSERT(d->active_fd.contains(sn->fd)); 143 | QSockFd &sd = d->active_fd[sn->fd]; 144 | if (sd.selected) { 145 | Q_ASSERT(sd.mask == 0); 146 | d->doWsaAsyncSelect(sn->fd, 0); 147 | sd.selected = false; 148 | } 149 | d->postActivateSocketNotifiers(); 150 | 151 | // Ignore the message if a notification with the same type was 152 | // received previously. Suppressed message is definitely spurious. 153 | const long eventCode = WSAGETSELECTEVENT(lp); 154 | if ((sd.mask & eventCode) != eventCode) { 155 | sd.mask |= eventCode; 156 | QEvent event(type < 3 ? QEvent::SockAct : QEvent::SockClose); 157 | QCoreApplication::sendEvent(sn->obj, &event); 158 | } 159 | } 160 | } 161 | return 0; 162 | } 163 | case WM_QT_ACTIVATENOTIFIERS: { 164 | Q_ASSERT(d != nullptr); 165 | 166 | // Postpone activation if we have unhandled socket notifier messages 167 | // in the queue. WM_QT_ACTIVATENOTIFIERS will be posted again as a result of 168 | // event processing. 169 | MSG msg; 170 | if (!PeekMessage(&msg, d->internalHwnd, 171 | WM_QT_SOCKETNOTIFIER, WM_QT_SOCKETNOTIFIER, PM_NOREMOVE) 172 | && d->queuedSocketEvents.isEmpty()) { 173 | // register all socket notifiers 174 | for (QSFDict::iterator it = d->active_fd.begin(), end = d->active_fd.end(); 175 | it != end; ++it) { 176 | QSockFd &sd = it.value(); 177 | if (!sd.selected) { 178 | d->doWsaAsyncSelect(it.key(), sd.event); 179 | // allow any event to be accepted 180 | sd.mask = 0; 181 | sd.selected = true; 182 | } 183 | } 184 | } 185 | d->activateNotifiersPosted = false; 186 | return 0; 187 | } 188 | case WM_TIMER: 189 | Q_ASSERT(d != nullptr); 190 | 191 | if (wp == d->sendPostedEventsTimerId) 192 | q->sendPostedEvents(); 193 | else 194 | d->sendTimerEvent(wp); 195 | return 0; 196 | case WM_QT_SENDPOSTEDEVENTS: 197 | Q_ASSERT(d != nullptr); 198 | 199 | // We send posted events manually, if the window procedure was invoked 200 | // by the foreign event loop (e.g. from the native modal dialog). 201 | // Skip sending, if the message queue is not empty. 202 | // sendPostedEventsTimer will deliver posted events later. 203 | static const UINT mask = QS_ALLEVENTS; 204 | if (HIWORD(GetQueueStatus(mask)) == 0) 205 | q->sendPostedEvents(); 206 | else 207 | d->startPostedEventsTimer(); 208 | return 0; 209 | } // switch (message) 210 | 211 | return DefWindowProc(hwnd, message, wp, lp); 212 | } 213 | 214 | void QEventDispatcherWin32Private::startPostedEventsTimer() 215 | { 216 | // we received WM_QT_SENDPOSTEDEVENTS, so allow posting it again 217 | wakeUps.storeRelaxed(0); 218 | if (sendPostedEventsTimerId == 0) { 219 | // Start a timer to deliver posted events when the message queue is emptied. 220 | sendPostedEventsTimerId = SetTimer(internalHwnd, SendPostedEventsTimerId, 221 | USER_TIMER_MINIMUM, NULL); 222 | } 223 | } 224 | 225 | // Provide class name and atom for the message window used by 226 | // QEventDispatcherWin32Private via Q_GLOBAL_STATIC shared between threads. 227 | struct QWindowsMessageWindowClassContext 228 | { 229 | QWindowsMessageWindowClassContext(); 230 | ~QWindowsMessageWindowClassContext(); 231 | 232 | ATOM atom; 233 | wchar_t *className; 234 | }; 235 | 236 | QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext() 237 | : atom(0), className(0) 238 | { 239 | // make sure that multiple Qt's can coexist in the same process 240 | const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget") 241 | + QString::number(quintptr(qt_internal_proc)); 242 | className = new wchar_t[qClassName.size() + 1]; 243 | qClassName.toWCharArray(className); 244 | className[qClassName.size()] = 0; 245 | 246 | WNDCLASS wc; 247 | wc.style = 0; 248 | wc.lpfnWndProc = qt_internal_proc; 249 | wc.cbClsExtra = 0; 250 | wc.cbWndExtra = 0; 251 | wc.hInstance = GetModuleHandle(0); 252 | wc.hIcon = 0; 253 | wc.hCursor = 0; 254 | wc.hbrBackground = 0; 255 | wc.lpszMenuName = NULL; 256 | wc.lpszClassName = className; 257 | atom = RegisterClass(&wc); 258 | if (!atom) { 259 | qErrnoWarning("%ls RegisterClass() failed", qUtf16Printable(qClassName)); 260 | delete [] className; 261 | className = 0; 262 | } 263 | } 264 | 265 | QWindowsMessageWindowClassContext::~QWindowsMessageWindowClassContext() 266 | { 267 | if (className) { 268 | UnregisterClass(className, GetModuleHandle(0)); 269 | delete [] className; 270 | } 271 | } 272 | 273 | Q_GLOBAL_STATIC(QWindowsMessageWindowClassContext, qWindowsMessageWindowClassContext) 274 | 275 | static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher) 276 | { 277 | QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext(); 278 | if (!ctx->atom) 279 | return 0; 280 | HWND wnd = CreateWindow(ctx->className, // classname 281 | ctx->className, // window name 282 | 0, // style 283 | 0, 0, 0, 0, // geometry 284 | HWND_MESSAGE, // parent 285 | 0, // menu handle 286 | GetModuleHandle(0), // application 287 | 0); // windows creation data. 288 | 289 | if (!wnd) { 290 | qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed"); 291 | return 0; 292 | } 293 | 294 | SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast(eventDispatcher)); 295 | 296 | return wnd; 297 | } 298 | 299 | static ULONG calculateNextTimeout(WinTimerInfo *t, quint64 currentTime) 300 | { 301 | uint interval = t->interval; 302 | ULONG tolerance = TIMERV_DEFAULT_COALESCING; 303 | switch (t->timerType) { 304 | case Qt::PreciseTimer: 305 | // high precision timer is based on millisecond precision 306 | // so no adjustment is necessary 307 | break; 308 | 309 | case Qt::CoarseTimer: 310 | // this timer has up to 5% coarseness 311 | // so our boundaries are 20 ms and 20 s 312 | // below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision 313 | // above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer 314 | if (interval >= 20000) { 315 | t->timerType = Qt::VeryCoarseTimer; 316 | } else if (interval <= 20) { 317 | // no adjustment necessary 318 | t->timerType = Qt::PreciseTimer; 319 | break; 320 | } else { 321 | tolerance = interval / 20; 322 | break; 323 | } 324 | Q_FALLTHROUGH(); 325 | case Qt::VeryCoarseTimer: 326 | // the very coarse timer is based on full second precision, 327 | // so we round to closest second (but never to zero) 328 | tolerance = 1000; 329 | if (interval < 1000) 330 | interval = 1000; 331 | else 332 | interval = (interval + 500) / 1000 * 1000; 333 | currentTime = currentTime / 1000 * 1000; 334 | break; 335 | } 336 | 337 | t->interval = interval; 338 | t->timeout = currentTime + interval; 339 | return tolerance; 340 | } 341 | 342 | void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t) 343 | { 344 | Q_ASSERT(internalHwnd); 345 | 346 | Q_Q(QEventDispatcherWin32); 347 | 348 | bool ok = false; 349 | ULONG tolerance = calculateNextTimeout(t, qt_msectime()); 350 | uint interval = t->interval; 351 | if (interval == 0u) { 352 | // optimization for single-shot-zero-timer 353 | QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId)); 354 | ok = true; 355 | } else if (tolerance == TIMERV_DEFAULT_COALESCING) { 356 | // 3/2016: Although MSDN states timeSetEvent() is deprecated, the function 357 | // is still deemed to be the most reliable precision timer. 358 | t->fastTimerId = timeSetEvent(interval, 1, qt_fast_timer_proc, DWORD_PTR(t), 359 | TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS); 360 | ok = t->fastTimerId; 361 | } 362 | 363 | typedef BOOL (WINAPI *SetCoalescableTimerFunc) (HWND, UINT_PTR, UINT, TIMERPROC, ULONG); 364 | static SetCoalescableTimerFunc mySetCoalescableTimerFunc = 365 | (SetCoalescableTimerFunc)::GetProcAddress(::GetModuleHandle(L"User32"), "SetCoalescableTimer"); 366 | 367 | if (!ok && mySetCoalescableTimerFunc) { 368 | // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available 369 | ok = mySetCoalescableTimerFunc(internalHwnd, t->timerId, interval, nullptr, tolerance); 370 | } 371 | 372 | if (!ok) 373 | ok = SetTimer(internalHwnd, t->timerId, interval, nullptr); 374 | 375 | if (!ok) 376 | qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer"); 377 | } 378 | 379 | void QEventDispatcherWin32Private::unregisterTimer(WinTimerInfo *t) 380 | { 381 | if (t->interval == 0) { 382 | QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId); 383 | } else if (t->fastTimerId != 0) { 384 | timeKillEvent(t->fastTimerId); 385 | QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId); 386 | } else { 387 | KillTimer(internalHwnd, t->timerId); 388 | } 389 | t->timerId = -1; 390 | if (!t->inTimerEvent) 391 | delete t; 392 | } 393 | 394 | void QEventDispatcherWin32Private::sendTimerEvent(int timerId) 395 | { 396 | WinTimerInfo *t = timerDict.value(timerId); 397 | if (t && !t->inTimerEvent) { 398 | // send event, but don't allow it to recurse 399 | t->inTimerEvent = true; 400 | 401 | // recalculate next emission 402 | calculateNextTimeout(t, qt_msectime()); 403 | 404 | QTimerEvent e(t->timerId); 405 | QCoreApplication::sendEvent(t->obj, &e); 406 | 407 | // timer could have been removed 408 | if (t->timerId == -1) { 409 | delete t; 410 | } else { 411 | t->inTimerEvent = false; 412 | } 413 | } 414 | } 415 | 416 | void QEventDispatcherWin32Private::doWsaAsyncSelect(qintptr socket, long event) 417 | { 418 | Q_ASSERT(internalHwnd); 419 | // BoundsChecker may emit a warning for WSAAsyncSelect when event == 0 420 | // This is a BoundsChecker bug and not a Qt bug 421 | WSAAsyncSelect(socket, internalHwnd, event ? int(WM_QT_SOCKETNOTIFIER) : 0, event); 422 | } 423 | 424 | void QEventDispatcherWin32Private::postActivateSocketNotifiers() 425 | { 426 | if (!activateNotifiersPosted) 427 | activateNotifiersPosted = PostMessage(internalHwnd, WM_QT_ACTIVATENOTIFIERS, 0, 0); 428 | } 429 | 430 | QEventDispatcherWin32::QEventDispatcherWin32(QObject *parent) 431 | : QEventDispatcherWin32(*new QEventDispatcherWin32Private, parent) 432 | { 433 | } 434 | 435 | QEventDispatcherWin32::QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent) 436 | : QAbstractEventDispatcher(dd, parent) 437 | { 438 | Q_D(QEventDispatcherWin32); 439 | 440 | d->internalHwnd = qt_create_internal_window(this); 441 | } 442 | 443 | QEventDispatcherWin32::~QEventDispatcherWin32() 444 | { 445 | } 446 | 447 | static bool isUserInputMessage(UINT message) 448 | { 449 | return (message >= WM_KEYFIRST && message <= WM_KEYLAST) 450 | || (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) 451 | || message == WM_MOUSEWHEEL 452 | || message == WM_MOUSEHWHEEL 453 | || message == WM_TOUCH 454 | #ifndef QT_NO_GESTURES 455 | || message == WM_GESTURE 456 | || message == WM_GESTURENOTIFY 457 | #endif 458 | // Pointer input: Exclude WM_NCPOINTERUPDATE .. WM_POINTERROUTEDRELEASED 459 | || (message >= 0x0241 && message <= 0x0253) 460 | || message == WM_CLOSE; 461 | } 462 | 463 | bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) 464 | { 465 | Q_D(QEventDispatcherWin32); 466 | 467 | // We don't know _when_ the interrupt occurred so we have to honor it. 468 | const bool wasInterrupted = d->interrupt.fetchAndStoreRelaxed(false); 469 | emit awake(); 470 | 471 | // To prevent livelocks, send posted events once per iteration. 472 | // QCoreApplication::sendPostedEvents() takes care about recursions. 473 | sendPostedEvents(); 474 | 475 | if (wasInterrupted) 476 | return false; 477 | 478 | auto threadData = d->threadData.loadRelaxed(); 479 | bool canWait; 480 | bool retVal = false; 481 | do { 482 | QVarLengthArray processedTimers; 483 | while (!d->interrupt.loadRelaxed()) { 484 | MSG msg; 485 | 486 | if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) { 487 | // process queued user input events 488 | msg = d->queuedUserInputEvents.takeFirst(); 489 | } else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) { 490 | // process queued socket events 491 | msg = d->queuedSocketEvents.takeFirst(); 492 | } else if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { 493 | if (flags.testFlag(QEventLoop::ExcludeUserInputEvents) 494 | && isUserInputMessage(msg.message)) { 495 | // queue user input events for later processing 496 | d->queuedUserInputEvents.append(msg); 497 | continue; 498 | } 499 | if ((flags & QEventLoop::ExcludeSocketNotifiers) 500 | && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) { 501 | // queue socket events for later processing 502 | d->queuedSocketEvents.append(msg); 503 | continue; 504 | } 505 | } else if (MsgWaitForMultipleObjectsEx(0, NULL, 0, QS_ALLINPUT, MWMO_ALERTABLE) 506 | == WAIT_OBJECT_0) { 507 | // a new message has arrived, process it 508 | continue; 509 | } else { 510 | // nothing to do, so break 511 | break; 512 | } 513 | 514 | if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) { 515 | d->startPostedEventsTimer(); 516 | // Set result to 'true' because the message was sent by wakeUp(). 517 | retVal = true; 518 | continue; 519 | } 520 | if (msg.message == WM_TIMER) { 521 | // Skip timer event intended for use inside foreign loop. 522 | if (d->internalHwnd == msg.hwnd && msg.wParam == d->sendPostedEventsTimerId) 523 | continue; 524 | 525 | // avoid live-lock by keeping track of the timers we've already sent 526 | bool found = false; 527 | for (int i = 0; !found && i < processedTimers.count(); ++i) { 528 | const MSG processed = processedTimers.constData()[i]; 529 | found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam); 530 | } 531 | if (found) 532 | continue; 533 | processedTimers.append(msg); 534 | } else if (msg.message == WM_QUIT) { 535 | if (QCoreApplication::instance()) 536 | QCoreApplication::instance()->quit(); 537 | return false; 538 | } 539 | 540 | if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) { 541 | TranslateMessage(&msg); 542 | DispatchMessage(&msg); 543 | } 544 | retVal = true; 545 | } 546 | 547 | // wait for message 548 | canWait = (!retVal 549 | && !d->interrupt.loadRelaxed() 550 | && flags.testFlag(QEventLoop::WaitForMoreEvents) 551 | && threadData->canWaitLocked()); 552 | if (canWait) { 553 | emit aboutToBlock(); 554 | MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); 555 | emit awake(); 556 | } 557 | } while (canWait); 558 | 559 | return retVal; 560 | } 561 | 562 | void QEventDispatcherWin32::registerSocketNotifier(QSocketNotifier *notifier) 563 | { 564 | Q_ASSERT(notifier); 565 | qintptr sockfd = notifier->socket(); 566 | int type = notifier->type(); 567 | #ifndef QT_NO_DEBUG 568 | if (sockfd < 0) { 569 | qWarning("QEventDispatcherWin32::registerSocketNotifier: invalid socket identifier"); 570 | return; 571 | } 572 | if (notifier->thread() != thread() || thread() != QThread::currentThread()) { 573 | qWarning("QEventDispatcherWin32: socket notifiers cannot be enabled from another thread"); 574 | return; 575 | } 576 | #endif 577 | 578 | Q_D(QEventDispatcherWin32); 579 | QSNDict *sn_vec[3] = { &d->sn_read, &d->sn_write, &d->sn_except }; 580 | QSNDict *dict = sn_vec[type]; 581 | 582 | if (QCoreApplication::closingDown()) // ### d->exitloop? 583 | return; // after sn_cleanup, don't reinitialize. 584 | 585 | if (dict->contains(sockfd)) { 586 | const char *t[] = { "Read", "Write", "Exception" }; 587 | /* Variable "socket" below is a function pointer. */ 588 | qWarning("QSocketNotifier: Multiple socket notifiers for " 589 | "same socket %" PRIdQINTPTR " and type %s", sockfd, t[type]); 590 | } 591 | 592 | QSockNot *sn = new QSockNot; 593 | sn->obj = notifier; 594 | sn->fd = sockfd; 595 | dict->insert(sn->fd, sn); 596 | 597 | long event = 0; 598 | if (d->sn_read.contains(sockfd)) 599 | event |= FD_READ | FD_CLOSE | FD_ACCEPT; 600 | if (d->sn_write.contains(sockfd)) 601 | event |= FD_WRITE | FD_CONNECT; 602 | if (d->sn_except.contains(sockfd)) 603 | event |= FD_OOB; 604 | 605 | QSFDict::iterator it = d->active_fd.find(sockfd); 606 | if (it != d->active_fd.end()) { 607 | QSockFd &sd = it.value(); 608 | if (sd.selected) { 609 | d->doWsaAsyncSelect(sockfd, 0); 610 | sd.selected = false; 611 | } 612 | sd.event |= event; 613 | } else { 614 | // Although WSAAsyncSelect(..., 0), which is called from 615 | // unregisterSocketNotifier(), immediately disables event message 616 | // posting for the socket, it is possible that messages could be 617 | // waiting in the application message queue even if the socket was 618 | // closed. Also, some events could be implicitly re-enabled due 619 | // to system calls. Ignore these superfluous events until all 620 | // pending notifications have been suppressed. Next activation of 621 | // socket notifiers will reset the mask. 622 | d->active_fd.insert(sockfd, QSockFd(event, FD_READ | FD_CLOSE | FD_ACCEPT | FD_WRITE 623 | | FD_CONNECT | FD_OOB)); 624 | } 625 | 626 | d->postActivateSocketNotifiers(); 627 | } 628 | 629 | void QEventDispatcherWin32::unregisterSocketNotifier(QSocketNotifier *notifier) 630 | { 631 | Q_ASSERT(notifier); 632 | #ifndef QT_NO_DEBUG 633 | qintptr sockfd = notifier->socket(); 634 | if (sockfd < 0) { 635 | qWarning("QEventDispatcherWin32::unregisterSocketNotifier: invalid socket identifier"); 636 | return; 637 | } 638 | if (notifier->thread() != thread() || thread() != QThread::currentThread()) { 639 | qWarning("QEventDispatcherWin32: socket notifiers cannot be disabled from another thread"); 640 | return; 641 | } 642 | #endif 643 | doUnregisterSocketNotifier(notifier); 644 | } 645 | 646 | void QEventDispatcherWin32::doUnregisterSocketNotifier(QSocketNotifier *notifier) 647 | { 648 | Q_D(QEventDispatcherWin32); 649 | int type = notifier->type(); 650 | qintptr sockfd = notifier->socket(); 651 | Q_ASSERT(sockfd >= 0); 652 | 653 | QSFDict::iterator it = d->active_fd.find(sockfd); 654 | if (it != d->active_fd.end()) { 655 | QSockFd &sd = it.value(); 656 | if (sd.selected) 657 | d->doWsaAsyncSelect(sockfd, 0); 658 | const long event[3] = { FD_READ | FD_CLOSE | FD_ACCEPT, FD_WRITE | FD_CONNECT, FD_OOB }; 659 | sd.event ^= event[type]; 660 | if (sd.event == 0) { 661 | d->active_fd.erase(it); 662 | } else if (sd.selected) { 663 | sd.selected = false; 664 | d->postActivateSocketNotifiers(); 665 | } 666 | } 667 | 668 | QSNDict *sn_vec[3] = { &d->sn_read, &d->sn_write, &d->sn_except }; 669 | QSNDict *dict = sn_vec[type]; 670 | QSockNot *sn = dict->value(sockfd); 671 | if (!sn) 672 | return; 673 | 674 | dict->remove(sockfd); 675 | delete sn; 676 | } 677 | 678 | void QEventDispatcherWin32::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object) 679 | { 680 | #ifndef QT_NO_DEBUG 681 | if (timerId < 1 || interval < 0 || !object) { 682 | qWarning("QEventDispatcherWin32::registerTimer: invalid arguments"); 683 | return; 684 | } 685 | if (object->thread() != thread() || thread() != QThread::currentThread()) { 686 | qWarning("QEventDispatcherWin32::registerTimer: timers cannot be started from another thread"); 687 | return; 688 | } 689 | #endif 690 | 691 | Q_D(QEventDispatcherWin32); 692 | 693 | // exiting ... do not register new timers 694 | // (QCoreApplication::closingDown() is set too late to be used here) 695 | if (d->closingDown) 696 | return; 697 | 698 | WinTimerInfo *t = new WinTimerInfo; 699 | t->dispatcher = this; 700 | t->timerId = timerId; 701 | t->interval = interval; 702 | t->timerType = timerType; 703 | t->obj = object; 704 | t->inTimerEvent = false; 705 | t->fastTimerId = 0; 706 | 707 | d->registerTimer(t); 708 | 709 | d->timerDict.insert(t->timerId, t); // store timers in dict 710 | } 711 | 712 | bool QEventDispatcherWin32::unregisterTimer(int timerId) 713 | { 714 | #ifndef QT_NO_DEBUG 715 | if (timerId < 1) { 716 | qWarning("QEventDispatcherWin32::unregisterTimer: invalid argument"); 717 | return false; 718 | } 719 | if (thread() != QThread::currentThread()) { 720 | qWarning("QEventDispatcherWin32::unregisterTimer: timers cannot be stopped from another thread"); 721 | return false; 722 | } 723 | #endif 724 | 725 | Q_D(QEventDispatcherWin32); 726 | 727 | WinTimerInfo *t = d->timerDict.take(timerId); 728 | if (!t) 729 | return false; 730 | 731 | d->unregisterTimer(t); 732 | return true; 733 | } 734 | 735 | bool QEventDispatcherWin32::unregisterTimers(QObject *object) 736 | { 737 | #ifndef QT_NO_DEBUG 738 | if (!object) { 739 | qWarning("QEventDispatcherWin32::unregisterTimers: invalid argument"); 740 | return false; 741 | } 742 | if (object->thread() != thread() || thread() != QThread::currentThread()) { 743 | qWarning("QEventDispatcherWin32::unregisterTimers: timers cannot be stopped from another thread"); 744 | return false; 745 | } 746 | #endif 747 | 748 | Q_D(QEventDispatcherWin32); 749 | if (d->timerDict.isEmpty()) 750 | return false; 751 | 752 | auto it = d->timerDict.begin(); 753 | while (it != d->timerDict.end()) { 754 | WinTimerInfo *t = it.value(); 755 | Q_ASSERT(t); 756 | if (t->obj == object) { 757 | it = d->timerDict.erase(it); 758 | d->unregisterTimer(t); 759 | } else { 760 | ++it; 761 | } 762 | } 763 | return true; 764 | } 765 | 766 | QList 767 | QEventDispatcherWin32::registeredTimers(QObject *object) const 768 | { 769 | #ifndef QT_NO_DEBUG 770 | if (!object) { 771 | qWarning("QEventDispatcherWin32:registeredTimers: invalid argument"); 772 | return QList(); 773 | } 774 | #endif 775 | 776 | Q_D(const QEventDispatcherWin32); 777 | QList list; 778 | for (WinTimerInfo *t : std::as_const(d->timerDict)) { 779 | Q_ASSERT(t); 780 | if (t->obj == object) 781 | list << TimerInfo(t->timerId, t->interval, t->timerType); 782 | } 783 | return list; 784 | } 785 | 786 | int QEventDispatcherWin32::remainingTime(int timerId) 787 | { 788 | #ifndef QT_NO_DEBUG 789 | if (timerId < 1) { 790 | qWarning("QEventDispatcherWin32::remainingTime: invalid argument"); 791 | return -1; 792 | } 793 | #endif 794 | 795 | Q_D(QEventDispatcherWin32); 796 | 797 | quint64 currentTime = qt_msectime(); 798 | 799 | WinTimerInfo *t = d->timerDict.value(timerId); 800 | if (t) { 801 | // timer found, return time to wait 802 | return t->timeout > currentTime ? t->timeout - currentTime : 0; 803 | } 804 | 805 | #ifndef QT_NO_DEBUG 806 | qWarning("QEventDispatcherWin32::remainingTime: timer id %d not found", timerId); 807 | #endif 808 | 809 | return -1; 810 | } 811 | 812 | void QEventDispatcherWin32::wakeUp() 813 | { 814 | Q_D(QEventDispatcherWin32); 815 | if (d->wakeUps.testAndSetRelaxed(0, 1)) { 816 | // post a WM_QT_SENDPOSTEDEVENTS to this thread if there isn't one already pending 817 | if (!PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0)) 818 | qErrnoWarning("QEventDispatcherWin32::wakeUp: Failed to post a message"); 819 | } 820 | } 821 | 822 | void QEventDispatcherWin32::interrupt() 823 | { 824 | Q_D(QEventDispatcherWin32); 825 | d->interrupt.storeRelaxed(true); 826 | wakeUp(); 827 | } 828 | 829 | void QEventDispatcherWin32::startingUp() 830 | { } 831 | 832 | void QEventDispatcherWin32::closingDown() 833 | { 834 | Q_D(QEventDispatcherWin32); 835 | 836 | // clean up any socketnotifiers 837 | while (!d->sn_read.isEmpty()) 838 | doUnregisterSocketNotifier((*(d->sn_read.begin()))->obj); 839 | while (!d->sn_write.isEmpty()) 840 | doUnregisterSocketNotifier((*(d->sn_write.begin()))->obj); 841 | while (!d->sn_except.isEmpty()) 842 | doUnregisterSocketNotifier((*(d->sn_except.begin()))->obj); 843 | Q_ASSERT(d->active_fd.isEmpty()); 844 | 845 | // clean up any timers 846 | for (WinTimerInfo *t : std::as_const(d->timerDict)) 847 | d->unregisterTimer(t); 848 | d->timerDict.clear(); 849 | 850 | d->closingDown = true; 851 | 852 | if (d->sendPostedEventsTimerId != 0) 853 | KillTimer(d->internalHwnd, d->sendPostedEventsTimerId); 854 | d->sendPostedEventsTimerId = 0; 855 | } 856 | 857 | bool QEventDispatcherWin32::event(QEvent *e) 858 | { 859 | Q_D(QEventDispatcherWin32); 860 | switch (e->type()) { 861 | case QEvent::ZeroTimerEvent: { 862 | QZeroTimerEvent *zte = static_cast(e); 863 | WinTimerInfo *t = d->timerDict.value(zte->timerId()); 864 | if (t) { 865 | t->inTimerEvent = true; 866 | 867 | QTimerEvent te(zte->timerId()); 868 | QCoreApplication::sendEvent(t->obj, &te); 869 | 870 | // timer could have been removed 871 | if (t->timerId == -1) { 872 | delete t; 873 | } else { 874 | if (t->interval == 0 && t->inTimerEvent) { 875 | // post the next zero timer event as long as the timer was not restarted 876 | QCoreApplication::postEvent(this, new QZeroTimerEvent(zte->timerId())); 877 | } 878 | 879 | t->inTimerEvent = false; 880 | } 881 | } 882 | return true; 883 | } 884 | case QEvent::Timer: 885 | d->sendTimerEvent(static_cast(e)->timerId()); 886 | break; 887 | default: 888 | break; 889 | } 890 | return QAbstractEventDispatcher::event(e); 891 | } 892 | 893 | void QEventDispatcherWin32::sendPostedEvents() 894 | { 895 | Q_D(QEventDispatcherWin32); 896 | 897 | if (d->sendPostedEventsTimerId != 0) 898 | KillTimer(d->internalHwnd, d->sendPostedEventsTimerId); 899 | d->sendPostedEventsTimerId = 0; 900 | 901 | // Allow posting WM_QT_SENDPOSTEDEVENTS message. 902 | d->wakeUps.storeRelaxed(0); 903 | 904 | QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed()); 905 | } 906 | 907 | HWND QEventDispatcherWin32::internalHwnd() 908 | { 909 | return d_func()->internalHwnd; 910 | } 911 | 912 | QT_END_NAMESPACE 913 | 914 | #include "moc_qeventdispatcher_win_p.cpp" 915 | -------------------------------------------------------------------------------- /qtbase/src/corelib/thread/qmutex.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // Copyright (C) 2016 Intel Corporation. 3 | // Copyright (C) 2012 Olivier Goffart 4 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 5 | 6 | #include "global/qglobal.h" 7 | #include "qplatformdefs.h" 8 | #include "qmutex.h" 9 | #include 10 | #include "qatomic.h" 11 | #include "qfutex_p.h" 12 | #include "qthread.h" 13 | #include "qmutex_p.h" 14 | 15 | #ifndef QT_ALWAYS_USE_FUTEX 16 | #include "private/qfreelist_p.h" 17 | #endif 18 | 19 | QT_BEGIN_NAMESPACE 20 | 21 | using namespace QtFutex; 22 | static inline QMutexPrivate *dummyFutexValue() 23 | { 24 | return reinterpret_cast(quintptr(3)); 25 | } 26 | 27 | /* 28 | \class QBasicMutex 29 | \inmodule QtCore 30 | \brief QMutex POD 31 | \internal 32 | 33 | \ingroup thread 34 | 35 | - Can be used as global static object. 36 | - Always non-recursive 37 | - Do not use tryLock with timeout > 0, else you can have a leak (see the ~QMutex destructor) 38 | */ 39 | 40 | /*! 41 | \class QMutex 42 | \inmodule QtCore 43 | \brief The QMutex class provides access serialization between threads. 44 | 45 | \threadsafe 46 | 47 | \ingroup thread 48 | 49 | The purpose of a QMutex is to protect an object, data structure or 50 | section of code so that only one thread can access it at a time 51 | (this is similar to the Java \c synchronized keyword). It is 52 | usually best to use a mutex with a QMutexLocker since this makes 53 | it easy to ensure that locking and unlocking are performed 54 | consistently. 55 | 56 | For example, say there is a method that prints a message to the 57 | user on two lines: 58 | 59 | \snippet code/src_corelib_thread_qmutex.cpp 0 60 | 61 | If these two methods are called in succession, the following happens: 62 | 63 | \snippet code/src_corelib_thread_qmutex.cpp 1 64 | 65 | If these two methods are called simultaneously from two threads then the 66 | following sequence could result: 67 | 68 | \snippet code/src_corelib_thread_qmutex.cpp 2 69 | 70 | If we add a mutex, we should get the result we want: 71 | 72 | \snippet code/src_corelib_thread_qmutex.cpp 3 73 | 74 | Then only one thread can modify \c number at any given time and 75 | the result is correct. This is a trivial example, of course, but 76 | applies to any other case where things need to happen in a 77 | particular sequence. 78 | 79 | When you call lock() in a thread, other threads that try to call 80 | lock() in the same place will block until the thread that got the 81 | lock calls unlock(). A non-blocking alternative to lock() is 82 | tryLock(). 83 | 84 | QMutex is optimized to be fast in the non-contended case. It 85 | will not allocate memory if there is no contention on that mutex. 86 | It is constructed and destroyed with almost no overhead, 87 | which means it is fine to have many mutexes as part of other classes. 88 | 89 | \sa QRecursiveMutex, QMutexLocker, QReadWriteLock, QSemaphore, QWaitCondition 90 | */ 91 | 92 | /*! 93 | \fn QMutex::QMutex() 94 | 95 | Constructs a new mutex. The mutex is created in an unlocked state. 96 | */ 97 | 98 | /*! \fn QMutex::~QMutex() 99 | 100 | Destroys the mutex. 101 | 102 | \warning Destroying a locked mutex may result in undefined behavior. 103 | */ 104 | void QBasicMutex::destroyInternal(QMutexPrivate *d) 105 | { 106 | if (!d) 107 | return; 108 | if (!futexAvailable()) { 109 | if (d != dummyLocked() && d->possiblyUnlocked.loadRelaxed() && tryLock()) { 110 | unlock(); 111 | return; 112 | } 113 | } 114 | qWarning("QMutex: destroying locked mutex"); 115 | } 116 | 117 | /*! \fn void QMutex::lock() 118 | 119 | Locks the mutex. If another thread has locked the mutex then this 120 | call will block until that thread has unlocked it. 121 | 122 | Calling this function multiple times on the same mutex from the 123 | same thread will cause a \e dead-lock. 124 | 125 | \sa unlock() 126 | */ 127 | 128 | /*! \fn bool QMutex::tryLock(int timeout) 129 | 130 | Attempts to lock the mutex. This function returns \c true if the lock 131 | was obtained; otherwise it returns \c false. If another thread has 132 | locked the mutex, this function will wait for at most \a timeout 133 | milliseconds for the mutex to become available. 134 | 135 | Note: Passing a negative number as the \a timeout is equivalent to 136 | calling lock(), i.e. this function will wait forever until mutex 137 | can be locked if \a timeout is negative. 138 | 139 | If the lock was obtained, the mutex must be unlocked with unlock() 140 | before another thread can successfully lock it. 141 | 142 | Calling this function multiple times on the same mutex from the 143 | same thread will cause a \e dead-lock. 144 | 145 | \sa lock(), unlock() 146 | */ 147 | 148 | /*! \fn bool QMutex::tryLock(QDeadlineTimer timer) 149 | \since 6.6 150 | 151 | Attempts to lock the mutex. This function returns \c true if the lock 152 | was obtained; otherwise it returns \c false. If another thread has 153 | locked the mutex, this function will wait until \a timer expires 154 | for the mutex to become available. 155 | 156 | If the lock was obtained, the mutex must be unlocked with unlock() 157 | before another thread can successfully lock it. 158 | 159 | Calling this function multiple times on the same mutex from the 160 | same thread will cause a \e dead-lock. 161 | 162 | \sa lock(), unlock() 163 | */ 164 | 165 | /*! \fn bool QMutex::tryLock() 166 | \overload 167 | 168 | Attempts to lock the mutex. This function returns \c true if the lock 169 | was obtained; otherwise it returns \c false. 170 | 171 | If the lock was obtained, the mutex must be unlocked with unlock() 172 | before another thread can successfully lock it. 173 | 174 | Calling this function multiple times on the same mutex from the 175 | same thread will cause a \e dead-lock. 176 | 177 | \sa lock(), unlock() 178 | */ 179 | 180 | /*! \fn bool QMutex::try_lock() 181 | \since 5.8 182 | 183 | Attempts to lock the mutex. This function returns \c true if the lock 184 | was obtained; otherwise it returns \c false. 185 | 186 | This function is provided for compatibility with the Standard Library 187 | concept \c Lockable. It is equivalent to tryLock(). 188 | */ 189 | 190 | /*! \fn template bool QMutex::try_lock_for(std::chrono::duration duration) 191 | \since 5.8 192 | 193 | Attempts to lock the mutex. This function returns \c true if the lock 194 | was obtained; otherwise it returns \c false. If another thread has 195 | locked the mutex, this function will wait for at least \a duration 196 | for the mutex to become available. 197 | 198 | Note: Passing a negative duration as the \a duration is equivalent to 199 | calling try_lock(). This behavior differs from tryLock(). 200 | 201 | If the lock was obtained, the mutex must be unlocked with unlock() 202 | before another thread can successfully lock it. 203 | 204 | Calling this function multiple times on the same mutex from the 205 | same thread will cause a \e dead-lock. 206 | 207 | \sa lock(), unlock() 208 | */ 209 | 210 | /*! \fn template bool QMutex::try_lock_until(std::chrono::time_point timePoint) 211 | \since 5.8 212 | 213 | Attempts to lock the mutex. This function returns \c true if the lock 214 | was obtained; otherwise it returns \c false. If another thread has 215 | locked the mutex, this function will wait at least until \a timePoint 216 | for the mutex to become available. 217 | 218 | Note: Passing a \a timePoint which has already passed is equivalent 219 | to calling try_lock(). This behavior differs from tryLock(). 220 | 221 | If the lock was obtained, the mutex must be unlocked with unlock() 222 | before another thread can successfully lock it. 223 | 224 | Calling this function multiple times on the same mutex from the 225 | same thread will cause a \e dead-lock. 226 | 227 | \sa lock(), unlock() 228 | */ 229 | 230 | /*! \fn void QMutex::unlock() 231 | 232 | Unlocks the mutex. Attempting to unlock a mutex in a different 233 | thread to the one that locked it results in an error. Unlocking a 234 | mutex that is not locked results in undefined behavior. 235 | 236 | \sa lock() 237 | */ 238 | 239 | /*! 240 | \class QRecursiveMutex 241 | \inmodule QtCore 242 | \since 5.14 243 | \brief The QRecursiveMutex class provides access serialization between threads. 244 | 245 | \threadsafe 246 | 247 | \ingroup thread 248 | 249 | The QRecursiveMutex class is a mutex, like QMutex, with which it is 250 | API-compatible. It differs from QMutex by accepting lock() calls from 251 | the same thread any number of times. QMutex would deadlock in this situation. 252 | 253 | QRecursiveMutex is much more expensive to construct and operate on, so 254 | use a plain QMutex whenever you can. Sometimes, one public function, 255 | however, calls another public function, and they both need to lock the 256 | same mutex. In this case, you have two options: 257 | 258 | \list 259 | \li Factor the code that needs mutex protection into private functions, 260 | which assume that the mutex is held when they are called, and lock a 261 | plain QMutex in the public functions before you call the private 262 | implementation ones. 263 | \li Or use a recursive mutex, so it doesn't matter that the first public 264 | function has already locked the mutex when the second one wishes to do so. 265 | \endlist 266 | 267 | \sa QMutex, QMutexLocker, QReadWriteLock, QSemaphore, QWaitCondition 268 | */ 269 | 270 | /*! \fn QRecursiveMutex::QRecursiveMutex() 271 | 272 | Constructs a new recursive mutex. The mutex is created in an unlocked state. 273 | 274 | \sa lock(), unlock() 275 | */ 276 | 277 | /*! 278 | Destroys the mutex. 279 | 280 | \warning Destroying a locked mutex may result in undefined behavior. 281 | */ 282 | QRecursiveMutex::~QRecursiveMutex() 283 | { 284 | } 285 | 286 | /*! \fn void QRecursiveMutex::lock() 287 | 288 | Locks the mutex. If another thread has locked the mutex then this 289 | call will block until that thread has unlocked it. 290 | 291 | Calling this function multiple times on the same mutex from the 292 | same thread is allowed. 293 | 294 | \sa unlock() 295 | */ 296 | 297 | /*! 298 | \fn QRecursiveMutex::tryLock(int timeout) 299 | 300 | Attempts to lock the mutex. This function returns \c true if the lock 301 | was obtained; otherwise it returns \c false. If another thread has 302 | locked the mutex, this function will wait for at most \a timeout 303 | milliseconds for the mutex to become available. 304 | 305 | Note: Passing a negative number as the \a timeout is equivalent to 306 | calling lock(), i.e. this function will wait forever until mutex 307 | can be locked if \a timeout is negative. 308 | 309 | If the lock was obtained, the mutex must be unlocked with unlock() 310 | before another thread can successfully lock it. 311 | 312 | Calling this function multiple times on the same mutex from the 313 | same thread is allowed. 314 | 315 | \sa lock(), unlock() 316 | */ 317 | 318 | /*! 319 | \since 6.6 320 | 321 | Attempts to lock the mutex. This function returns \c true if the lock 322 | was obtained; otherwise it returns \c false. If another thread has 323 | locked the mutex, this function will wait until \a timeout expires 324 | for the mutex to become available. 325 | 326 | If the lock was obtained, the mutex must be unlocked with unlock() 327 | before another thread can successfully lock it. 328 | 329 | Calling this function multiple times on the same mutex from the 330 | same thread is allowed. 331 | 332 | \sa lock(), unlock() 333 | */ 334 | bool QRecursiveMutex::tryLock(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT 335 | { 336 | unsigned tsanFlags = QtTsan::MutexWriteReentrant | QtTsan::TryLock; 337 | QtTsan::mutexPreLock(this, tsanFlags); 338 | 339 | Qt::HANDLE self = QThread::currentThreadId(); 340 | if (owner.loadRelaxed() == self) { 341 | ++count; 342 | Q_ASSERT_X(count != 0, "QMutex::lock", "Overflow in recursion counter"); 343 | QtTsan::mutexPostLock(this, tsanFlags, 0); 344 | return true; 345 | } 346 | bool success = true; 347 | if (timeout.isForever()) { 348 | mutex.lock(); 349 | } else { 350 | success = mutex.tryLock(timeout); 351 | } 352 | 353 | if (success) 354 | owner.storeRelaxed(self); 355 | else 356 | tsanFlags |= QtTsan::TryLockFailed; 357 | 358 | QtTsan::mutexPostLock(this, tsanFlags, 0); 359 | 360 | return success; 361 | } 362 | 363 | /*! \fn bool QRecursiveMutex::try_lock() 364 | \since 5.8 365 | 366 | Attempts to lock the mutex. This function returns \c true if the lock 367 | was obtained; otherwise it returns \c false. 368 | 369 | This function is provided for compatibility with the Standard Library 370 | concept \c Lockable. It is equivalent to tryLock(). 371 | */ 372 | 373 | /*! \fn template bool QRecursiveMutex::try_lock_for(std::chrono::duration duration) 374 | \since 5.8 375 | 376 | Attempts to lock the mutex. This function returns \c true if the lock 377 | was obtained; otherwise it returns \c false. If another thread has 378 | locked the mutex, this function will wait for at least \a duration 379 | for the mutex to become available. 380 | 381 | Note: Passing a negative duration as the \a duration is equivalent to 382 | calling try_lock(). This behavior differs from tryLock(). 383 | 384 | If the lock was obtained, the mutex must be unlocked with unlock() 385 | before another thread can successfully lock it. 386 | 387 | Calling this function multiple times on the same mutex from the 388 | same thread is allowed. 389 | 390 | \sa lock(), unlock() 391 | */ 392 | 393 | /*! \fn template bool QRecursiveMutex::try_lock_until(std::chrono::time_point timePoint) 394 | \since 5.8 395 | 396 | Attempts to lock the mutex. This function returns \c true if the lock 397 | was obtained; otherwise it returns \c false. If another thread has 398 | locked the mutex, this function will wait at least until \a timePoint 399 | for the mutex to become available. 400 | 401 | Note: Passing a \a timePoint which has already passed is equivalent 402 | to calling try_lock(). This behavior differs from tryLock(). 403 | 404 | If the lock was obtained, the mutex must be unlocked with unlock() 405 | before another thread can successfully lock it. 406 | 407 | Calling this function multiple times on the same mutex from the 408 | same thread is allowed. 409 | 410 | \sa lock(), unlock() 411 | */ 412 | 413 | /*! 414 | Unlocks the mutex. Attempting to unlock a mutex in a different 415 | thread to the one that locked it results in an error. Unlocking a 416 | mutex that is not locked results in undefined behavior. 417 | 418 | \sa lock() 419 | */ 420 | void QRecursiveMutex::unlock() noexcept 421 | { 422 | Q_ASSERT(owner.loadRelaxed() == QThread::currentThreadId()); 423 | QtTsan::mutexPreUnlock(this, 0u); 424 | 425 | if (count > 0) { 426 | count--; 427 | } else { 428 | owner.storeRelaxed(nullptr); 429 | mutex.unlock(); 430 | } 431 | 432 | QtTsan::mutexPostUnlock(this, 0u); 433 | } 434 | 435 | 436 | /*! 437 | \class QMutexLocker 438 | \inmodule QtCore 439 | \brief The QMutexLocker class is a convenience class that simplifies 440 | locking and unlocking mutexes. 441 | 442 | \threadsafe 443 | 444 | \ingroup thread 445 | 446 | Locking and unlocking a QMutex or QRecursiveMutex in complex functions and 447 | statements or in exception handling code is error-prone and 448 | difficult to debug. QMutexLocker can be used in such situations 449 | to ensure that the state of the mutex is always well-defined. 450 | 451 | QMutexLocker should be created within a function where a 452 | QMutex needs to be locked. The mutex is locked when QMutexLocker 453 | is created. You can unlock and relock the mutex with \c unlock() 454 | and \c relock(). If locked, the mutex will be unlocked when the 455 | QMutexLocker is destroyed. 456 | 457 | For example, this complex function locks a QMutex upon entering 458 | the function and unlocks the mutex at all the exit points: 459 | 460 | \snippet code/src_corelib_thread_qmutex.cpp 4 461 | 462 | This example function will get more complicated as it is 463 | developed, which increases the likelihood that errors will occur. 464 | 465 | Using QMutexLocker greatly simplifies the code, and makes it more 466 | readable: 467 | 468 | \snippet code/src_corelib_thread_qmutex.cpp 5 469 | 470 | Now, the mutex will always be unlocked when the QMutexLocker 471 | object is destroyed (when the function returns since \c locker is 472 | an auto variable). 473 | 474 | The same principle applies to code that throws and catches 475 | exceptions. An exception that is not caught in the function that 476 | has locked the mutex has no way of unlocking the mutex before the 477 | exception is passed up the stack to the calling function. 478 | 479 | QMutexLocker also provides a \c mutex() member function that returns 480 | the mutex on which the QMutexLocker is operating. This is useful 481 | for code that needs access to the mutex, such as 482 | QWaitCondition::wait(). For example: 483 | 484 | \snippet code/src_corelib_thread_qmutex.cpp 6 485 | 486 | \sa QReadLocker, QWriteLocker, QMutex 487 | */ 488 | 489 | /*! 490 | \fn template QMutexLocker::QMutexLocker(Mutex *mutex) noexcept 491 | 492 | Constructs a QMutexLocker and locks \a mutex. The mutex will be 493 | unlocked when the QMutexLocker is destroyed. If \a mutex is \nullptr, 494 | QMutexLocker does nothing. 495 | 496 | \sa QMutex::lock() 497 | */ 498 | 499 | /*! 500 | \fn template QMutexLocker::QMutexLocker(QMutexLocker &&other) noexcept 501 | \since 6.4 502 | 503 | Move-constructs a QMutexLocker from \a other. The mutex and the 504 | state of \a other is transferred to the newly constructed instance. 505 | After the move, \a other will no longer be managing any mutex. 506 | 507 | \sa QMutex::lock() 508 | */ 509 | 510 | /*! 511 | \fn template QMutexLocker &QMutexLocker::operator=(QMutexLocker &&other) noexcept 512 | \since 6.4 513 | 514 | Move-assigns \a other onto this QMutexLocker. If this QMutexLocker 515 | was holding a locked mutex before the assignment, the mutex will be 516 | unlocked. The mutex and the state of \a other is then transferred 517 | to this QMutexLocker. After the move, \a other will no longer be 518 | managing any mutex. 519 | 520 | \sa QMutex::lock() 521 | */ 522 | 523 | /*! 524 | \fn template void QMutexLocker::swap(QMutexLocker &other) noexcept 525 | \since 6.4 526 | 527 | Swaps the mutex and the state of this QMutexLocker with \a other. 528 | This operation is very fast and never fails. 529 | 530 | \sa QMutex::lock() 531 | */ 532 | 533 | /*! 534 | \fn template QMutexLocker::~QMutexLocker() noexcept 535 | 536 | Destroys the QMutexLocker and unlocks the mutex that was locked 537 | in the constructor. 538 | 539 | \sa QMutex::unlock() 540 | */ 541 | 542 | /*! 543 | \fn template bool QMutexLocker::isLocked() const noexcept 544 | \since 6.4 545 | 546 | Returns true if this QMutexLocker is currently locking its associated 547 | mutex, or false otherwise. 548 | */ 549 | 550 | /*! 551 | \fn template void QMutexLocker::unlock() noexcept 552 | 553 | Unlocks this mutex locker. You can use \c relock() to lock 554 | it again. It does not need to be locked when destroyed. 555 | 556 | \sa relock() 557 | */ 558 | 559 | /*! 560 | \fn template void QMutexLocker::relock() noexcept 561 | 562 | Relocks an unlocked mutex locker. 563 | 564 | \sa unlock() 565 | */ 566 | 567 | /*! 568 | \fn template QMutex *QMutexLocker::mutex() const 569 | 570 | Returns the mutex on which the QMutexLocker is operating. 571 | 572 | */ 573 | 574 | /* 575 | For a rough introduction on how this works, refer to 576 | http://woboq.com/blog/internals-of-qmutex-in-qt5.html 577 | which explains a slightly simplified version of it. 578 | The differences are that here we try to work with timeout (requires the 579 | possiblyUnlocked flag) and that we only wake one thread when unlocking 580 | (requires maintaining the waiters count) 581 | We also support recursive mutexes which always have a valid d_ptr. 582 | 583 | The waiters flag represents the number of threads that are waiting or about 584 | to wait on the mutex. There are two tricks to keep in mind: 585 | We don't want to increment waiters after we checked no threads are waiting 586 | (waiters == 0). That's why we atomically set the BigNumber flag on waiters when 587 | we check waiters. Similarly, if waiters is decremented right after we checked, 588 | the mutex would be unlocked (d->wakeUp() has (or will) be called), but there is 589 | no thread waiting. This is only happening if there was a timeout in tryLock at the 590 | same time as the mutex is unlocked. So when there was a timeout, we set the 591 | possiblyUnlocked flag. 592 | */ 593 | 594 | /* 595 | * QBasicMutex implementation with futexes (Linux, Windows 10) 596 | * 597 | * QBasicMutex contains one pointer value, which can contain one of four 598 | * different values: 599 | * 0x0 unlocked 600 | * 0x1 locked, no waiters 601 | * 0x3 locked, at least one waiter 602 | * 603 | * LOCKING: 604 | * 605 | * A starts in the 0x0 state, indicating that it's unlocked. When the first 606 | * thread attempts to lock it, it will perform a testAndSetAcquire 607 | * from 0x0 to 0x1. If that succeeds, the caller concludes that it 608 | * successfully locked the mutex. That happens in fastTryLock(). 609 | * 610 | * If that testAndSetAcquire fails, QBasicMutex::lockInternal is called. 611 | * 612 | * lockInternal will examine the value of the pointer. Otherwise, it will use 613 | * futexes to sleep and wait for another thread to unlock. To do that, it needs 614 | * to set a pointer value of 0x3, which indicates that thread is waiting. It 615 | * does that by a simple fetchAndStoreAcquire operation. 616 | * 617 | * If the pointer value was 0x0, it means we succeeded in acquiring the mutex. 618 | * For other values, it will then call FUTEX_WAIT and with an expected value of 619 | * 0x3. 620 | * 621 | * If the pointer value changed before futex(2) managed to sleep, it will 622 | * return -1 / EWOULDBLOCK, in which case we have to start over. And even if we 623 | * are woken up directly by a FUTEX_WAKE, we need to acquire the mutex, so we 624 | * start over again. 625 | * 626 | * UNLOCKING: 627 | * 628 | * To unlock, we need to set a value of 0x0 to indicate it's unlocked. The 629 | * first attempt is a testAndSetRelease operation from 0x1 to 0x0. If that 630 | * succeeds, we're done. 631 | * 632 | * If it fails, unlockInternal() is called. The only possibility is that the 633 | * mutex value was 0x3, which indicates some other thread is waiting or was 634 | * waiting in the past. We then set the mutex to 0x0 and perform a FUTEX_WAKE. 635 | */ 636 | 637 | /*! 638 | \internal helper for lock() 639 | */ 640 | Q_NEVER_INLINE 641 | void QBasicMutex::lockInternal() QT_MUTEX_LOCK_NOEXCEPT 642 | { 643 | if (futexAvailable()) { 644 | // note we must set to dummyFutexValue because there could be other threads 645 | // also waiting 646 | while (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) != nullptr) { 647 | // successfully set the waiting bit, now sleep 648 | futexWait(d_ptr, dummyFutexValue()); 649 | 650 | // we got woken up, so try to acquire the mutex 651 | } 652 | Q_ASSERT(d_ptr.loadRelaxed()); 653 | } else { 654 | lockInternal(QDeadlineTimer::Forever); 655 | } 656 | } 657 | 658 | /*! 659 | \internal helper for lock(int) 660 | */ 661 | #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) 662 | bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT 663 | { 664 | if (timeout == 0) 665 | return false; 666 | 667 | return lockInternal(QDeadlineTimer(timeout)); 668 | } 669 | #endif 670 | 671 | /*! 672 | \internal helper for tryLock(QDeadlineTimer) 673 | */ 674 | Q_NEVER_INLINE 675 | bool QBasicMutex::lockInternal(QDeadlineTimer deadlineTimer) QT_MUTEX_LOCK_NOEXCEPT 676 | { 677 | if (deadlineTimer.hasExpired()) 678 | return false; 679 | 680 | if (futexAvailable()) { 681 | if (Q_UNLIKELY(deadlineTimer.isForever())) { 682 | lockInternal(); 683 | return true; 684 | } 685 | 686 | // The mutex is already locked, set a bit indicating we're waiting. 687 | // Note we must set to dummyFutexValue because there could be other threads 688 | // also waiting. 689 | if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr) 690 | return true; 691 | 692 | for (;;) { 693 | if (!futexWait(d_ptr, dummyFutexValue(), deadlineTimer)) 694 | return false; 695 | 696 | // We got woken up, so must try to acquire the mutex. We must set 697 | // to dummyFutexValue() again because there could be other threads 698 | // waiting. 699 | if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr) 700 | return true; 701 | 702 | if (deadlineTimer.hasExpired()) 703 | return false; 704 | } 705 | } 706 | 707 | #if !defined(QT_ALWAYS_USE_FUTEX) 708 | while (!fastTryLock()) { 709 | QMutexPrivate *copy = d_ptr.loadAcquire(); 710 | if (!copy) // if d is 0, the mutex is unlocked 711 | continue; 712 | 713 | if (copy == dummyLocked()) { 714 | if (deadlineTimer.hasExpired()) 715 | return false; 716 | // The mutex is locked but does not have a QMutexPrivate yet. 717 | // we need to allocate a QMutexPrivate 718 | QMutexPrivate *newD = QMutexPrivate::allocate(); 719 | if (!d_ptr.testAndSetOrdered(dummyLocked(), newD)) { 720 | //Either the mutex is already unlocked, or another thread already set it. 721 | newD->deref(); 722 | continue; 723 | } 724 | copy = newD; 725 | //the d->refCount is already 1 the deref will occurs when we unlock 726 | } 727 | 728 | QMutexPrivate *d = static_cast(copy); 729 | if (deadlineTimer.hasExpired() && !d->possiblyUnlocked.loadRelaxed()) 730 | return false; 731 | 732 | // At this point we have a pointer to a QMutexPrivate. But the other thread 733 | // may unlock the mutex at any moment and release the QMutexPrivate to the pool. 734 | // We will try to reference it to avoid unlock to release it to the pool to make 735 | // sure it won't be released. But if the refcount is already 0 it has been released. 736 | if (!d->ref()) 737 | continue; //that QMutexPrivate was already released 738 | 739 | // We now hold a reference to the QMutexPrivate. It won't be released and re-used. 740 | // But it is still possible that it was already re-used by another QMutex right before 741 | // we did the ref(). So check if we still hold a pointer to the right mutex. 742 | if (d != d_ptr.loadAcquire()) { 743 | //Either the mutex is already unlocked, or relocked with another mutex 744 | d->deref(); 745 | continue; 746 | } 747 | 748 | // In this part, we will try to increment the waiters count. 749 | // We just need to take care of the case in which the old_waiters 750 | // is set to the BigNumber magic value set in unlockInternal() 751 | int old_waiters; 752 | do { 753 | old_waiters = d->waiters.loadAcquire(); 754 | if (old_waiters == -QMutexPrivate::BigNumber) { 755 | // we are unlocking, and the thread that unlocks is about to change d to 0 756 | // we try to acquire the mutex by changing to dummyLocked() 757 | if (d_ptr.testAndSetAcquire(d, dummyLocked())) { 758 | // Mutex acquired 759 | d->deref(); 760 | return true; 761 | } else { 762 | Q_ASSERT(d != d_ptr.loadRelaxed()); //else testAndSetAcquire should have succeeded 763 | // Mutex is likely to bo 0, we should continue the outer-loop, 764 | // set old_waiters to the magic value of BigNumber 765 | old_waiters = QMutexPrivate::BigNumber; 766 | break; 767 | } 768 | } 769 | } while (!d->waiters.testAndSetRelaxed(old_waiters, old_waiters + 1)); 770 | 771 | if (d != d_ptr.loadAcquire()) { 772 | // The mutex was unlocked before we incremented waiters. 773 | if (old_waiters != QMutexPrivate::BigNumber) { 774 | //we did not break the previous loop 775 | Q_ASSERT(d->waiters.loadRelaxed() >= 1); 776 | d->waiters.deref(); 777 | } 778 | d->deref(); 779 | continue; 780 | } 781 | 782 | if (d->wait(deadlineTimer)) { 783 | // reset the possiblyUnlocked flag if needed (and deref its corresponding reference) 784 | if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false)) 785 | d->deref(); 786 | d->derefWaiters(1); 787 | //we got the lock. (do not deref) 788 | Q_ASSERT(d == d_ptr.loadRelaxed()); 789 | return true; 790 | } else { 791 | // timed out 792 | d->derefWaiters(1); 793 | //There may be a race in which the mutex is unlocked right after we timed out, 794 | // and before we deref the waiters, so maybe the mutex is actually unlocked. 795 | // Set the possiblyUnlocked flag to indicate this possibility. 796 | if (!d->possiblyUnlocked.testAndSetRelaxed(false, true)) { 797 | // We keep a reference when possiblyUnlocked is true. 798 | // but if possiblyUnlocked was already true, we don't need to keep the reference. 799 | d->deref(); 800 | } 801 | return false; 802 | } 803 | } 804 | Q_ASSERT(d_ptr.loadRelaxed() != 0); 805 | return true; 806 | #else 807 | Q_UNREACHABLE(); 808 | #endif 809 | } 810 | 811 | /*! 812 | \internal 813 | */ 814 | Q_NEVER_INLINE 815 | void QBasicMutex::unlockInternal() noexcept 816 | { 817 | QMutexPrivate *copy = d_ptr.loadAcquire(); 818 | Q_ASSERT(copy); //we must be locked 819 | Q_ASSERT(copy != dummyLocked()); // testAndSetRelease(dummyLocked(), 0) failed 820 | 821 | if (futexAvailable()) { 822 | d_ptr.storeRelease(nullptr); 823 | return futexWakeOne(d_ptr); 824 | } 825 | 826 | #if !defined(QT_ALWAYS_USE_FUTEX) 827 | QMutexPrivate *d = reinterpret_cast(copy); 828 | 829 | // If no one is waiting for the lock anymore, we should reset d to 0x0. 830 | // Using fetchAndAdd, we atomically check that waiters was equal to 0, and add a flag 831 | // to the waiters variable (BigNumber). That way, we avoid the race in which waiters is 832 | // incremented right after we checked, because we won't increment waiters if is 833 | // equal to -BigNumber 834 | if (d->waiters.fetchAndAddRelease(-QMutexPrivate::BigNumber) == 0) { 835 | //there is no one waiting on this mutex anymore, set the mutex as unlocked (d = 0) 836 | if (d_ptr.testAndSetRelease(d, 0)) { 837 | // reset the possiblyUnlocked flag if needed (and deref its corresponding reference) 838 | if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false)) 839 | d->deref(); 840 | } 841 | d->derefWaiters(0); 842 | } else { 843 | d->derefWaiters(0); 844 | //there are thread waiting, transfer the lock. 845 | d->wakeUp(); 846 | } 847 | d->deref(); 848 | #else 849 | Q_UNUSED(copy); 850 | #endif 851 | } 852 | 853 | #if !defined(QT_ALWAYS_USE_FUTEX) 854 | //The freelist management 855 | namespace { 856 | struct FreeListConstants : QFreeListDefaultConstants { 857 | enum { BlockCount = 4, MaxIndex=0xffff }; 858 | static const int Sizes[BlockCount]; 859 | }; 860 | Q_CONSTINIT const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = { 861 | 16, 862 | 128, 863 | 1024, 864 | FreeListConstants::MaxIndex - (16 + 128 + 1024) 865 | }; 866 | 867 | typedef QFreeList FreeList; 868 | // We cannot use Q_GLOBAL_STATIC because it uses QMutex 869 | Q_CONSTINIT static FreeList freeList_; 870 | FreeList *freelist() 871 | { 872 | return &freeList_; 873 | } 874 | } 875 | 876 | QMutexPrivate *QMutexPrivate::allocate() 877 | { 878 | int i = freelist()->next(); 879 | QMutexPrivate *d = &(*freelist())[i]; 880 | d->id = i; 881 | Q_ASSERT(d->refCount.loadRelaxed() == 0); 882 | Q_ASSERT(!d->possiblyUnlocked.loadRelaxed()); 883 | Q_ASSERT(d->waiters.loadRelaxed() == 0); 884 | d->refCount.storeRelaxed(1); 885 | return d; 886 | } 887 | 888 | void QMutexPrivate::release() 889 | { 890 | Q_ASSERT(refCount.loadRelaxed() == 0); 891 | Q_ASSERT(!possiblyUnlocked.loadRelaxed()); 892 | Q_ASSERT(waiters.loadRelaxed() == 0); 893 | freelist()->release(id); 894 | } 895 | 896 | // atomically subtract "value" to the waiters, and remove the QMutexPrivate::BigNumber flag 897 | void QMutexPrivate::derefWaiters(int value) noexcept 898 | { 899 | int old_waiters; 900 | int new_waiters; 901 | do { 902 | old_waiters = waiters.loadRelaxed(); 903 | new_waiters = old_waiters; 904 | if (new_waiters < 0) { 905 | new_waiters += QMutexPrivate::BigNumber; 906 | } 907 | new_waiters -= value; 908 | } while (!waiters.testAndSetRelaxed(old_waiters, new_waiters)); 909 | } 910 | #endif 911 | 912 | QT_END_NAMESPACE 913 | 914 | #if defined(QT_ALWAYS_USE_FUTEX) 915 | // nothing 916 | #elif defined(Q_OS_DARWIN) 917 | # include "qmutex_mac.cpp" 918 | #elif defined(Q_OS_WIN) 919 | # include "qmutex_win.cpp" 920 | #else 921 | # include "qmutex_unix.cpp" 922 | #endif 923 | --------------------------------------------------------------------------------