Skip to content

Release the main window's native window when it is closed - #2555

Merged
m3nu merged 3 commits into
borgbase:masterfrom
ThomasWaldmann:fix-window-backing-store
Sep 7, 2026
Merged

Release the main window's native window when it is closed#2555
m3nu merged 3 commits into
borgbase:masterfrom
ThomasWaldmann:fix-window-backing-store

Conversation

@ThomasWaldmann

Copy link
Copy Markdown
Collaborator

Stacked on #2551: the first commit here is that PR's. Only the second commit ("Release the main window's native window when it is closed") belongs to this PR; once #2551 is merged this one reduces to it.

Description

Closing the main window only hides it. Qt keeps the native window and its backing store alive for as long as the widget exists — i.e. for the rest of the session once the user has opened the window and put Vorta back into the tray. On a Retina display that is 19–28 MB (depending on the window size) of the ~110 MB Vorta idles at after #2551.

MainWindow.closeEvent now schedules QWidget.destroy() for right after the close has gone through (Qt hides the widget only after closeEvent returns, hence the zero-timer). show() creates the native window again; the window comes back at its previous position and size, and nothing in Vorta touches the native handle (winId/windowHandle are unused).

Tearing the whole MainWindow down instead was measured too: it saves only ~3 MB more (the widgets and models) for a 140 ms rebuild plus all the app.main_window references, so it is not worth it.

Related Issue

Follow-up to #2551 (from the investigation for borgbackup/borg#10328).

Motivation and Context

Vorta is a tray-resident app; users open the window, start or check a backup, close it, and then it sits in the tray for days. The closed window's backing store was the largest remaining single item after #2551.

How Has This Been Tested?

  • New test tests/unit/test_main_window.py: the native window is gone after close() and back after reopening via open_main_window_action(). It mocks is_system_tray_available so closeEvent takes the tray path on headless CI. Passed on macOS with PyQt6 6.6.1 and 6.10.0, and on Linux/offscreen (Debian, Python 3.12, in a container).

  • Full unit suite on macOS: 266 passed, 7 skipped.

  • Footprint measured on macOS 15.7 with PyQt6 6.10 (the shipped stack), real event loop, window closed into the tray:

    physical footprint IOSurface
    window shown 108 MB 19 MB
    closed, before 108 MB 19 MB
    closed, with this change 89 MB 0
  • Position/size check: window moved and resized, closed, reopened from the tray → same position and size.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have read the CONTRIBUTING guide.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

I provide my contribution under the terms of the license of this repository and I affirm the Developer Certificate of Origin.

🤖 Generated with Claude Code

ThomasWaldmann and others added 2 commits September 5, 2026 19:44
…mory

`objc.loadBundle('Sparkle', ...)` defaults to `scan_classes=True`, which wraps
every Objective-C class in the process (~67,000 on macOS 15) as a Python class
and keeps them in this module's globals for the lifetime of the app. Only the
frozen app bundle takes this path, which is why the shipped Vorta.app idles at
~220-250 MB while a venv install of the same code idles at ~80-120 MB.

Load only the framework and look up `SUUpdater` explicitly instead.

Measured on macOS 15.7 (M3) with a bundle built from this tree, main window
hidden, 35 s after launch: physical footprint 228.8 MB -> 78.6 MB, realized
ObjC classes 66,928 -> 2,322.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Closing the main window only hides it: Qt keeps the native window and its
backing store alive for as long as the widget exists, i.e. for the rest of
the session once the user has opened the window and put Vorta back into
the tray. On a Retina display that is 19-28 MB (depending on the window
size) of the ~110 MB the app idles at after the Sparkle fix.

Call `QWidget.destroy()` once the close has gone through; `show()` creates
the native window again and the window comes back at its previous position
and size.

Measured (macOS 15.7, PyQt6 6.10, main window closed into the tray):
physical footprint 108 MB -> 89 MB, IOSurface 19 MB -> 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ThomasWaldmann
ThomasWaldmann marked this pull request as draft September 5, 2026 20:08
@ThomasWaldmann ThomasWaldmann changed the title Release the main window's native window when it is closed (~20-28 MB while in the tray) Release the main window's native window when it is closed Sep 5, 2026
@ThomasWaldmann
ThomasWaldmann marked this pull request as ready for review September 5, 2026 20:47
@m3nu
m3nu merged commit c26a171 into borgbase:master Sep 7, 2026
5 checks passed
@ThomasWaldmann
ThomasWaldmann deleted the fix-window-backing-store branch September 7, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants