diff --git a/src/vorta/views/main_window.py b/src/vorta/views/main_window.py index 761559201..96a87a9ec 100644 --- a/src/vorta/views/main_window.py +++ b/src/vorta/views/main_window.py @@ -3,7 +3,7 @@ from pathlib import Path from PyQt6 import QtCore, uic -from PyQt6.QtCore import QPoint, Qt +from PyQt6.QtCore import QPoint, Qt, QTimer from PyQt6.QtCore import pyqtSignal as Signal from PyQt6.QtGui import QFontMetrics, QKeySequence, QShortcut from PyQt6.QtWidgets import ( @@ -380,3 +380,11 @@ def closeEvent(self, event): elif not SettingsModel.get(key="disable_background_state").value: self.app.quit() event.accept() + # Closing only hides the window: Qt keeps the native window and its backing store + # (~28 MB on a Retina display) alive while Vorta sits in the tray. Release them once + # the close has gone through (Qt hides the widget after this handler returns). + QTimer.singleShot(0, self._release_native_window) + + def _release_native_window(self): + if not self.isVisible(): + self.destroy() # show() creates the native window again diff --git a/tests/unit/test_main_window.py b/tests/unit/test_main_window.py new file mode 100644 index 000000000..c8dbada57 --- /dev/null +++ b/tests/unit/test_main_window.py @@ -0,0 +1,35 @@ +import pytest + + +def test_close_releases_native_window(qapp, qtbot, monkeypatch): + """ + Closing the main window must release its native window (and with it the backing store), + and showing it again must work as before. + """ + main = qapp.main_window + was_visible = main.isVisible() + # Take the tray path in closeEvent(), also where no tray exists (headless CI). + monkeypatch.setattr('vorta.views.main_window.is_system_tray_available', lambda: True) + + def shown(): + return main.isVisible() and main.windowHandle() is not None + + def released(): + return not main.isVisible() and main.windowHandle() is None + + qapp.open_main_window_action() + qtbot.waitUntil(shown, **pytest._wait_defaults) + + main.close() + qtbot.waitUntil(released, **pytest._wait_defaults) + + qapp.open_main_window_action() + qtbot.waitUntil(shown, **pytest._wait_defaults) + + main.close() + qtbot.waitUntil(released, **pytest._wait_defaults) + + # Leave the window as the other tests expect it. + if was_visible: + qapp.open_main_window_action() + qtbot.waitUntil(shown, **pytest._wait_defaults)