From d1294c04d4aabf739ffe7d1df57be34961e6589d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 Aug 2025 17:58:30 -0400 Subject: [PATCH 1/2] Qt: Fix HiDPI handling on X11/Windows With X11, there is only ever a single scale, regardless of monitors as in Wayland, so it's always the highest scale (i.e., it's 2 if you have 1.5&1-scaled monitors). Thus we get no change events and don't update the internal scale. On Wayland though, as noted in the other issue from Qt devs, you only get the fractional scale after the first expose, so there's always at least one event there. In the older/pre-#30345 code path, in `showEvent`, we'd call `_update_screen` to set callbacks for its changes, and that _also_ called `_update_pixel_ratio`. In the new code, we don't have that initial call, and because Wayland always has at least one event at startup, it all seemed to work. So just add the `_update_pixel_ratio` call in the new code path as well. On X11, this will be the highest integer scale needed and never changes. On Wayland, this will also be the rounded-up integer scale, but if using fractional scaling, will change with a subsequent event to the correct one. This does cause a few extra changes on startup, but should be more consistent across platforms. --- lib/matplotlib/backends/backend_qt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 401ce0b0b754..dd614e516de5 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -273,6 +273,7 @@ def showEvent(self, event): window = self.window().windowHandle() current_version = tuple(int(x) for x in QtCore.qVersion().split('.', 2)[:2]) if current_version >= (6, 6): + self._update_pixel_ratio() window.installEventFilter(self) else: window.screenChanged.connect(self._update_screen) From a99b99d4e20cd8171b945626436f8e5fe3f24279 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 7 Aug 2025 04:11:58 -0400 Subject: [PATCH 2/2] TST: Simplify Qt pixel ratio test and ensure it tests initial scale --- lib/matplotlib/tests/test_backend_qt.py | 61 ++++++++++--------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index 6e147fd14380..db33986e9542 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -175,45 +175,30 @@ def set_device_pixel_ratio(ratio): assert qt_canvas.device_pixel_ratio == ratio qt_canvas.manager.show() + qt_canvas.draw() + qt_canvas.flush_events() size = qt_canvas.size() - set_device_pixel_ratio(3) - - # The DPI and the renderer width/height change - assert fig.dpi == 360 - assert qt_canvas.renderer.width == 1800 - assert qt_canvas.renderer.height == 720 - - # The actual widget size and figure logical size don't change. - assert size.width() == 600 - assert size.height() == 240 - assert qt_canvas.get_width_height() == (600, 240) - assert (fig.get_size_inches() == (5, 2)).all() - - set_device_pixel_ratio(2) - - # The DPI and the renderer width/height change - assert fig.dpi == 240 - assert qt_canvas.renderer.width == 1200 - assert qt_canvas.renderer.height == 480 - - # The actual widget size and figure logical size don't change. - assert size.width() == 600 - assert size.height() == 240 - assert qt_canvas.get_width_height() == (600, 240) - assert (fig.get_size_inches() == (5, 2)).all() - - set_device_pixel_ratio(1.5) - - # The DPI and the renderer width/height change - assert fig.dpi == 180 - assert qt_canvas.renderer.width == 900 - assert qt_canvas.renderer.height == 360 - - # The actual widget size and figure logical size don't change. - assert size.width() == 600 - assert size.height() == 240 - assert qt_canvas.get_width_height() == (600, 240) - assert (fig.get_size_inches() == (5, 2)).all() + + options = [ + (None, 360, 1800, 720), # Use ratio at startup time. + (3, 360, 1800, 720), # Change to same ratio. + (2, 240, 1200, 480), # Change to different ratio. + (1.5, 180, 900, 360), # Fractional ratio. + ] + for ratio, dpi, width, height in options: + if ratio is not None: + set_device_pixel_ratio(ratio) + + # The DPI and the renderer width/height change + assert fig.dpi == dpi + assert qt_canvas.renderer.width == width + assert qt_canvas.renderer.height == height + + # The actual widget size and figure logical size don't change. + assert size.width() == 600 + assert size.height() == 240 + assert qt_canvas.get_width_height() == (600, 240) + assert (fig.get_size_inches() == (5, 2)).all() @pytest.mark.backend('QtAgg', skip_on_importerror=True)