Windows: Decouple screen change monitoring from top level QWindows

The WM_DISPLAYCHANGE message it sent when displays are added, removed,
or update their properties such as the scale/DPI.

We were processing this message as part of QWindowsContext::windowsProc(),
which meant that we would only react to display changes if there was a
QWindow on screen. Just creating a QGuiApplication was insufficient to
pick up changes to screens after startup.

In addition, despite being documented to post messages to child windows,
WM_DISPLAYCHANGE only ends up in top level windows. Presumably it's the
top level window's responsibility to post the message to child windows.
As a result, if a QWindow was a native child window of a foreign window,
such as in audio plugins being hosted in a DAW, we would again fail to
pick up display changes.

We solve both these cases by decoupling the WM_DISPLAYCHANGE handling
from QWindowsContext::windowsProc(), by creating a dedicated window
for listening to WM_DISPLAYCHANGE. This is similar to how we already
handle tray icons, power notifications, clipboard, etc -- the only
difference being that since purely HWND_MESSAGE windows do not
receive WM_DISPLAYCHANGE it's an actual invisible WS_TILED window.

This also lets us remove the workaround for QTBUG-79248, which was
doing screen updates in response to WM_DPICHANGED when detecting
that there were no QWindows.

Task-number: QTBUG-103383
Task-number: QTBUG-79248
Fixes: QTBUG-102343
Change-Id: I905d8253069ec339b193edf05c052d21361ca3e9
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
(cherry picked from commit fa0b2ef81c0d22f4038235871fbc1abda55887d1)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Tor Arne Vestbø 2022-06-16 12:01:36 +02:00 committed by Qt Cherry-pick Bot
parent d41310a2c4
commit 7a3aa146a5
7 changed files with 44 additions and 14 deletions

View File

@ -140,8 +140,7 @@ enum WindowsEventType // Simplify event types
InputMethodRequest = InputMethodEventFlag + 6,
ThemeChanged = ThemingEventFlag + 1,
CompositionSettingsChanged = ThemingEventFlag + 2,
DisplayChangedEvent = 437,
SettingChangedEvent = DisplayChangedEvent + 1,
SettingChangedEvent = 438,
ScrollEvent = GenericEventFlag + 1,
ContextMenu = 123,
GestureEvent = 124,
@ -258,8 +257,6 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI
// http://msdn.microsoft.com/en-us/library/ms695534(v=vs.85).aspx
case WM_SETTINGCHANGE:
return QtWindows::SettingChangedEvent;
case WM_DISPLAYCHANGE:
return QtWindows::DisplayChangedEvent;
case WM_THEMECHANGED:
case WM_SYSCOLORCHANGE: // Handle color change as theme change (QTBUG-34170).
case WM_DWMCOLORIZATIONCOLORCHANGED:

View File

@ -71,6 +71,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
Q_LOGGING_CATEGORY(lcQpaUiAutomation, "qt.qpa.uiautomation")
Q_LOGGING_CATEGORY(lcQpaTrayIcon, "qt.qpa.trayicon")
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen")
int QWindowsContext::verbose = 0;
@ -1091,12 +1092,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
#else
return false;
#endif
case QtWindows::DisplayChangedEvent:
if (QWindowsTheme *t = QWindowsTheme::instance())
t->displayChanged();
QWindowsWindow::displayChanged();
d->m_screenManager.handleScreenChanges();
return false;
case QtWindows::SettingChangedEvent: {
QWindowsWindow::settingsChanged();
// Only refresh the window theme if the user changes the personalize settings.

View File

@ -31,6 +31,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaTablet)
Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility)
Q_DECLARE_LOGGING_CATEGORY(lcQpaUiAutomation)
Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon)
Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen)
class QWindow;
class QPlatformScreen;

View File

@ -264,7 +264,7 @@ QWindowsIntegration::QWindowsIntegration(const QStringList &paramList) :
#if QT_CONFIG(clipboard)
d->m_clipboard.registerViewer();
#endif
d->m_context.screenManager().handleScreenChanges();
d->m_context.screenManager().initialize();
d->m_context.setDetectAltGrModifier((d->m_options & DetectAltGrModifier) != 0);
}

View File

@ -513,8 +513,45 @@ QPlatformScreen::SubpixelAntialiasingType QWindowsScreen::subpixelAntialiasingTy
\internal
*/
extern "C" LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_DISPLAYCHANGE) {
qCDebug(lcQpaScreen) << "Handling WM_DISPLAYCHANGE";
if (QWindowsTheme *t = QWindowsTheme::instance())
t->displayChanged();
QWindowsWindow::displayChanged();
QWindowsContext::instance()->screenManager().handleScreenChanges();
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
QWindowsScreenManager::QWindowsScreenManager() = default;
void QWindowsScreenManager::initialize()
{
qCDebug(lcQpaScreen) << "Initializing screen manager";
auto className = QWindowsContext::instance()->registerWindowClass(
QWindowsContext::classNamePrefix() + QLatin1String("ScreenChangeObserverWindow"),
qDisplayChangeObserverWndProc);
// HWND_MESSAGE windows do not get WM_DISPLAYCHANGE, so we need to create
// a real top level window that we never show.
m_displayChangeObserver = CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
Q_ASSERT(m_displayChangeObserver);
qCDebug(lcQpaScreen) << "Created display change observer" << m_displayChangeObserver;
handleScreenChanges();
}
QWindowsScreenManager::~QWindowsScreenManager()
{
DestroyWindow(m_displayChangeObserver);
}
bool QWindowsScreenManager::isSingleScreen()
{

View File

@ -96,6 +96,8 @@ public:
using WindowsScreenList = QList<QWindowsScreen *>;
QWindowsScreenManager();
void initialize();
~QWindowsScreenManager();
void clearScreens();
@ -110,6 +112,7 @@ public:
private:
void removeScreen(int index);
HWND m_displayChangeObserver = nullptr;
WindowsScreenList m_screens;
};

View File

@ -94,9 +94,6 @@ static int indexOfHwnd(HWND hwnd)
extern "C" LRESULT QT_WIN_CALLBACK qWindowsTrayIconWndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
// QTBUG-79248: Trigger screen update if there are no other windows.
if (message == WM_DPICHANGED && QGuiApplication::topLevelWindows().isEmpty())
QWindowsContext::instance()->screenManager().handleScreenChanges();
if (message == MYWM_TASKBARCREATED || message == MYWM_NOTIFYICON
|| message == WM_INITMENU || message == WM_INITMENUPOPUP
|| message == WM_CLOSE || message == WM_COMMAND) {