From b6cd7ea2575ad4be2030cc3fa6bcb5b12fd34319 Mon Sep 17 00:00:00 2001 From: Lars Rustand Date: Sat, 4 Jan 2025 10:09:18 +0100 Subject: [PATCH 1/3] Improve last-visible and last-focused --- qutebrowser/app.py | 6 ++-- qutebrowser/mainwindow/mainwindow.py | 22 ++++++++----- qutebrowser/utils/objreg.py | 49 +++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 66bd485fc..54b7432a3 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -413,10 +413,8 @@ def on_focus_changed(_old, new): window = new.window() if isinstance(window, mainwindow.MainWindow): - objreg.register('last-focused-main-window', window, update=True) - # A focused window must also be visible, and in this case we should - # consider it as the most recently looked-at window - objreg.register('last-visible-main-window', window, update=True) + objreg.window_visibility_history.append(window) + objreg.window_focus_history.append(window) def open_desktopservices_url(url): diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 6e6821612..f06fb0b5a 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -623,13 +623,22 @@ class MainWindow(QWidget): self.tabbed_browser.widget.tab_bar().refresh() def showEvent(self, e): - """Extend showEvent to register us as the last-visible-main-window. + """Extend showEvent to update window-visibility-history. Args: e: The QShowEvent """ super().showEvent(e) - objreg.register('last-visible-main-window', self, update=True) + objreg.window_visibility_history.append(self) + + def hideEvent(self, e): + """Extend hideEvent to update window-visibility-history. + + Args: + e: The QHideEvent + """ + super().hideEvent(e) + objreg.window_visibility_history.remove(self) def _confirm_quit(self): """Confirm that this window should be closed. @@ -689,13 +698,8 @@ class MainWindow(QWidget): e.accept() - for key in ['last-visible-main-window', 'last-focused-main-window']: - try: - win = objreg.get(key) - if self is win: - objreg.delete(key) - except KeyError: - pass + objreg.window_focus_history.remove(self) + objreg.window_visibility_history.remove(self) sessions.session_manager.save_last_window_session() self._save_geometry() diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index c027b3cf6..a64e84556 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -7,6 +7,7 @@ import collections import functools +import weakref from typing import (TYPE_CHECKING, Any, Optional, Union) from collections.abc import MutableMapping, MutableSequence, Sequence, Callable @@ -38,6 +39,46 @@ class CommandOnlyError(Exception): """Raised when an object is requested which is used for commands only.""" +_WindowRefType = weakref.ReferenceType['mainwindow.MainWindow'] + + +class WindowAccessHistory(): + """Contains an ordered list of windows, sorted by last added. + + Uses weak references to avoid memory leaks. + """ + + def __init__(self) -> None: + self._windows: list[_WindowRefType] = [] + + def append(self, window: 'mainwindow.MainWindow') -> None: + """Append a window to the access history.""" + self.remove(window) + self._windows.append(weakref.ref(window)) + + def remove(self, window: 'mainwindow.MainWindow') -> None: + """Remove a window from the access history.""" + try: + self._windows.remove(weakref.ref(window)) + except ValueError: + pass + + def last(self) -> 'mainwindow.MainWindow': + """Return the last window from the access history. + + Prune any None values encountered and return the last still + existing window from the list. + """ + win = self._windows[-1]() + while win is None: + self._windows.pop() + win = self._windows[-1]() + return win + + +window_visibility_history = WindowAccessHistory() +window_focus_history = WindowAccessHistory() + _IndexType = Union[str, int] @@ -309,8 +350,8 @@ def dump_objects() -> Sequence[str]: def last_visible_window() -> 'mainwindow.MainWindow': """Get the last visible window, or the last focused window if none.""" try: - window = get('last-visible-main-window') - except KeyError: + window = window_visibility_history.last() + except IndexError: return last_focused_window() if window.tabbed_browser.is_shutting_down: return last_focused_window() @@ -320,8 +361,8 @@ def last_visible_window() -> 'mainwindow.MainWindow': def last_focused_window() -> 'mainwindow.MainWindow': """Get the last focused window, or the last window if none.""" try: - window = get('last-focused-main-window') - except KeyError: + window = window_focus_history.last() + except IndexError: return last_opened_window() if window.tabbed_browser.is_shutting_down: return last_opened_window() From 16b2b375a5316cc30b8caf45a53e2dc9be961ed4 Mon Sep 17 00:00:00 2001 From: Lars Rustand Date: Sun, 5 Jan 2025 19:27:07 +0100 Subject: [PATCH 2/3] Cleanup --- qutebrowser/utils/objreg.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index a64e84556..7c561b139 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -66,14 +66,16 @@ class WindowAccessHistory(): def last(self) -> 'mainwindow.MainWindow': """Return the last window from the access history. - Prune any None values encountered and return the last still - existing window from the list. + Prune any None values and windows that are shutting down and + return the last still existing window from the list. """ - win = self._windows[-1]() - while win is None: - self._windows.pop() - win = self._windows[-1]() - return win + for ref in reversed(self._windows): + win = ref() + if win is None or win.tabbed_browser.is_shutting_down: + self._windows.pop() + else: + return win + return None window_visibility_history = WindowAccessHistory() @@ -349,24 +351,12 @@ def dump_objects() -> Sequence[str]: def last_visible_window() -> 'mainwindow.MainWindow': """Get the last visible window, or the last focused window if none.""" - try: - window = window_visibility_history.last() - except IndexError: - return last_focused_window() - if window.tabbed_browser.is_shutting_down: - return last_focused_window() - return window + return window_visibility_history.last() or last_focused_window() def last_focused_window() -> 'mainwindow.MainWindow': """Get the last focused window, or the last window if none.""" - try: - window = window_focus_history.last() - except IndexError: - return last_opened_window() - if window.tabbed_browser.is_shutting_down: - return last_opened_window() - return window + return window_focus_history.last() or last_opened_window() def _window_by_index(idx: int) -> 'mainwindow.MainWindow': From f1a97218a85290dbfc23b8d89a43b4f23d8ffc1b Mon Sep 17 00:00:00 2001 From: Lars Rustand Date: Mon, 6 Jan 2025 19:32:31 +0100 Subject: [PATCH 3/3] Fix return type --- qutebrowser/utils/objreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 7c561b139..98cbe9cb2 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -63,7 +63,7 @@ class WindowAccessHistory(): except ValueError: pass - def last(self) -> 'mainwindow.MainWindow': + def last(self) -> Optional['mainwindow.MainWindow']: """Return the last window from the access history. Prune any None values and windows that are shutting down and