From 98df875a39af3d2727bb1bad782898186baff33d Mon Sep 17 00:00:00 2001 From: Alessio Attilio <317110166+alessio-attilio@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:02:46 +0200 Subject: [PATCH 1/5] fix: properly exit app when window is closed --- rare/components/__init__.py | 15 ++++++++++++++- rare/components/main_window.py | 5 +++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/rare/components/__init__.py b/rare/components/__init__.py index ac894e366..3c5640e33 100644 --- a/rare/components/__init__.py +++ b/rare/components/__init__.py @@ -1,9 +1,11 @@ import os import shutil +import signal from argparse import Namespace from datetime import datetime, timezone import requests.exceptions +import shiboken6 from PySide6.QtCore import Qt, QThreadPool, QTimer, Slot from PySide6.QtWidgets import QApplication @@ -49,10 +51,20 @@ def __init__(self, args: Namespace): self.launch_dialog: LaunchDialog | None = None self.relogin_timer: QTimer | None = None + signal.signal(signal.SIGINT, self._on_signal) + signal.signal(signal.SIGTERM, self._on_signal) + # This launches the application after it has been instantiated. # The timer's signal will be serviced once we call `exec()` on the application QTimer.singleShot(0, self.launch_app) + def _on_signal(self, signum: int, frame) -> None: + self.logger.info('%s received. Quitting Rare', signal.strsignal(signum)) + if self.main_window is not None and shiboken6.isValid(self.main_window): + self.main_window.exit_app.emit(0) + else: + self.quit() + def poke_timer(self): dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]).replace(tzinfo=timezone.utc) dt_now = datetime.now(timezone.utc) @@ -101,7 +113,8 @@ def _on_start_app(self): @Slot(int) def _on_exit_app(self, exit_code=0): threadpool = QThreadPool.globalInstance() - threadpool.waitForDone() + if not threadpool.waitForDone(5000): + self.logger.warning('Background workers did not finish in time, quitting anyway') if self.relogin_timer is not None: self.relogin_timer.stop() self.relogin_timer.deleteLater() diff --git a/rare/components/main_window.py b/rare/components/main_window.py index fca75ad6a..e91548c6f 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -247,8 +247,9 @@ def closeEvent(self, e: QCloseEvent) -> None: for qw in self.rcore.queued_workers(): self.rcore.dequeue_worker(qw) self.update_statusbar() - e.ignore() - return + else: + e.ignore() + return elif self.tab_widget.downloads_tab.is_download_active: reply = QMessageBox.question( self, From 5b49e84fcee863d29e1bf8d0f06dbc5289897ddd Mon Sep 17 00:00:00 2001 From: Alessio Attilio <317110166+alessio-attilio@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:26:59 +0200 Subject: [PATCH 2/5] fix: force exit on SIGINT/SIGTERM without waiting for workers --- rare/components/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rare/components/__init__.py b/rare/components/__init__.py index 3c5640e33..91eb80520 100644 --- a/rare/components/__init__.py +++ b/rare/components/__init__.py @@ -5,7 +5,6 @@ from datetime import datetime, timezone import requests.exceptions -import shiboken6 from PySide6.QtCore import Qt, QThreadPool, QTimer, Slot from PySide6.QtWidgets import QApplication @@ -50,6 +49,7 @@ def __init__(self, args: Namespace): self.main_window: RareWindow | None = None self.launch_dialog: LaunchDialog | None = None self.relogin_timer: QTimer | None = None + self._force_exit = False signal.signal(signal.SIGINT, self._on_signal) signal.signal(signal.SIGTERM, self._on_signal) @@ -59,11 +59,9 @@ def __init__(self, args: Namespace): QTimer.singleShot(0, self.launch_app) def _on_signal(self, signum: int, frame) -> None: - self.logger.info('%s received. Quitting Rare', signal.strsignal(signum)) - if self.main_window is not None and shiboken6.isValid(self.main_window): - self.main_window.exit_app.emit(0) - else: - self.quit() + self.logger.info('%s received. Forcibly quitting Rare', signal.strsignal(signum)) + self._force_exit = True + self._on_exit_app(0) def poke_timer(self): dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]).replace(tzinfo=timezone.utc) @@ -113,7 +111,10 @@ def _on_start_app(self): @Slot(int) def _on_exit_app(self, exit_code=0): threadpool = QThreadPool.globalInstance() - if not threadpool.waitForDone(5000): + if self._force_exit: + threadpool.clear() + self.logger.warning('Force exit: clearing all pending workers') + elif not threadpool.waitForDone(5000): self.logger.warning('Background workers did not finish in time, quitting anyway') if self.relogin_timer is not None: self.relogin_timer.stop() From 5d1cf30d0aa1f1f3989a36d4aa4e785affcf4174 Mon Sep 17 00:00:00 2001 From: Alessio Attilio <317110166+alessio-attilio@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:33:33 +0200 Subject: [PATCH 3/5] fix: remove timeout on normal exit, force exit handles impatient users --- rare/components/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rare/components/__init__.py b/rare/components/__init__.py index 91eb80520..a57d73a83 100644 --- a/rare/components/__init__.py +++ b/rare/components/__init__.py @@ -114,8 +114,8 @@ def _on_exit_app(self, exit_code=0): if self._force_exit: threadpool.clear() self.logger.warning('Force exit: clearing all pending workers') - elif not threadpool.waitForDone(5000): - self.logger.warning('Background workers did not finish in time, quitting anyway') + else: + threadpool.waitForDone() if self.relogin_timer is not None: self.relogin_timer.stop() self.relogin_timer.deleteLater() From 15e7b8d3d287b38c7316c03933e60f86feaddd6f Mon Sep 17 00:00:00 2001 From: Alessio Attilio <317110166+alessio-attilio@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:21:15 +0200 Subject: [PATCH 4/5] fix: handle SIGINT/SIGTERM with graceful window close --- rare/components/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/rare/components/__init__.py b/rare/components/__init__.py index a57d73a83..74cbcad25 100644 --- a/rare/components/__init__.py +++ b/rare/components/__init__.py @@ -49,7 +49,6 @@ def __init__(self, args: Namespace): self.main_window: RareWindow | None = None self.launch_dialog: LaunchDialog | None = None self.relogin_timer: QTimer | None = None - self._force_exit = False signal.signal(signal.SIGINT, self._on_signal) signal.signal(signal.SIGTERM, self._on_signal) @@ -59,9 +58,13 @@ def __init__(self, args: Namespace): QTimer.singleShot(0, self.launch_app) def _on_signal(self, signum: int, frame) -> None: - self.logger.info('%s received. Forcibly quitting Rare', signal.strsignal(signum)) - self._force_exit = True - self._on_exit_app(0) + self.logger.info('%s received. Initiating shutdown', signal.strsignal(signum)) + if self.main_window is not None: + self.main_window.close() + elif self.launch_dialog is not None: + self.launch_dialog.reject() + else: + self._on_exit_app(0) def poke_timer(self): dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]).replace(tzinfo=timezone.utc) @@ -111,11 +114,7 @@ def _on_start_app(self): @Slot(int) def _on_exit_app(self, exit_code=0): threadpool = QThreadPool.globalInstance() - if self._force_exit: - threadpool.clear() - self.logger.warning('Force exit: clearing all pending workers') - else: - threadpool.waitForDone() + threadpool.waitForDone() if self.relogin_timer is not None: self.relogin_timer.stop() self.relogin_timer.deleteLater() From c6c55f7c868cea06a4720d10ff9b2dd2c7512e29 Mon Sep 17 00:00:00 2001 From: Alessio Attilio <317110166+alessio-attilio@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:37:45 +0200 Subject: [PATCH 5/5] fix: always ignore close while active workers are running --- rare/components/main_window.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rare/components/main_window.py b/rare/components/main_window.py index e91548c6f..fca75ad6a 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -247,9 +247,8 @@ def closeEvent(self, e: QCloseEvent) -> None: for qw in self.rcore.queued_workers(): self.rcore.dequeue_worker(qw) self.update_statusbar() - else: - e.ignore() - return + e.ignore() + return elif self.tab_widget.downloads_tab.is_download_active: reply = QMessageBox.question( self,