diff --git a/rare/commands/launcher/__init__.py b/rare/commands/launcher/__init__.py
index 8e06ea0bde..636312efbc 100644
--- a/rare/commands/launcher/__init__.py
+++ b/rare/commands/launcher/__init__.py
@@ -29,7 +29,7 @@
from PySide6.QtWidgets import QApplication
from rare.lgndr.core import LegendaryCore
-from rare.models.base_game import RareGameSlim
+from rare.models.game_slim import RareGameSlim
from rare.models.launcher import Actions, BaseModel, ErrorModel, FinishedModel, StateChangedModel
from rare.shared.workers.cloud_sync import CloudSyncWorker
from rare.utils.paths import get_rare_executable
@@ -50,16 +50,18 @@
}
+class PreLaunchSignals(QObject):
+ ready_to_launch = Signal(LaunchParams)
+ pre_launch_command_started = Signal()
+ pre_launch_command_finished = Signal(int) # exit_code
+ error_occurred = Signal(str)
+
+
class PreLaunch(QRunnable):
- class Signals(QObject):
- ready_to_launch = Signal(LaunchParams)
- pre_launch_command_started = Signal()
- pre_launch_command_finished = Signal(int) # exit_code
- error_occurred = Signal(str)
def __init__(self, args: InitParams, rgame: RareGameSlim, sync_action=None):
super(PreLaunch, self).__init__()
- self.signals = self.Signals()
+ self.signals = PreLaunchSignals()
self.logger = getLogger(type(self).__name__)
self.args = args
self.rgame = rgame
@@ -76,8 +78,8 @@ def run(self) -> None:
if args := self.prepare_launch(self.args):
self.signals.ready_to_launch.emit(args)
- else:
- return
+ self.signals.disconnect(self.signals)
+ self.signals.deleteLater()
def prepare_launch(self, args: InitParams) -> Optional[LaunchParams]:
try:
@@ -92,7 +94,8 @@ def prepare_launch(self, args: InitParams) -> Optional[LaunchParams]:
proc = get_configured_qprocess(shlex.split(launch.pre_launch_command), launch.environment)
proc.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels)
proc.readyReadStandardOutput.connect(
- lambda: self.logger.info(str(proc.readAllStandardOutput().data(), "utf-8", "ignore"))
+ (lambda obj: obj.logger.debug(
+ str(proc.readAllStandardOutput().data(), "utf-8", "ignore"))).__get__(self)
)
self.signals.pre_launch_command_started.emit()
self.logger.info("Running pre-launch command %s, %s", proc.program(), proc.arguments())
@@ -102,17 +105,20 @@ def prepare_launch(self, args: InitParams) -> Optional[LaunchParams]:
proc.waitForFinished(-1)
else:
proc.startDetached()
+
return launch
+class SyncCheckWorkerSignals(QObject):
+ sync_state_ready = Signal()
+ error_occurred = Signal(str)
+
+
class SyncCheckWorker(QRunnable):
- class Signals(QObject):
- sync_state_ready = Signal()
- error_occurred = Signal(str)
def __init__(self, core: LegendaryCore, rgame: RareGameSlim):
super().__init__()
- self.signals = self.Signals()
+ self.signals = SyncCheckWorkerSignals()
self.core = core
self.rgame = rgame
@@ -123,6 +129,8 @@ def run(self) -> None:
self.signals.error_occurred.emit(str(e))
return
self.signals.sync_state_ready.emit()
+ self.signals.disconnect(self.signals)
+ self.signals.deleteLater()
class RareLauncherException(RareAppException):
@@ -171,26 +179,17 @@ def __init__(self, args: InitParams):
if args.show_console:
self.console = ConsoleDialog(game.app_title)
self.console.show()
- self.game_process.stateChanged.connect(
- lambda s: self.console.kill_button.setEnabled(
- self.game_process.state() == QProcess.ProcessState.Running
- )
- )
- self.game_process.stateChanged.connect(
- lambda s: self.console.terminate_button.setEnabled(
- self.game_process.state() == QProcess.ProcessState.Running
- )
- )
+ self.game_process.stateChanged.connect(self._on_game_process_changed)
self.sync_dialog: Optional[CloudSyncDialog] = None
self.game_process.finished.connect(self.__process_finished)
self.game_process.errorOccurred.connect(self.__process_errored)
if self.console:
- self.game_process.readyReadStandardOutput.connect(self.__proc_log_stdout)
- self.game_process.readyReadStandardError.connect(self.__proc_log_stderr)
- self.console.term.connect(self.__proc_term)
- self.console.kill.connect(self.__proc_kill)
+ self.game_process.readyReadStandardOutput.connect(self._proc_log_stdout)
+ self.game_process.readyReadStandardError.connect(self._proc_log_stderr)
+ self.console.term.connect(self._proc_term)
+ self.console.kill.connect(self._proc_kill)
ret = self.server.listen(f"rare_{args.app_name}")
if not ret:
@@ -207,23 +206,28 @@ def __init__(self, args: InitParams):
# The timer's signal will be serviced once we call `exec()` on the application
QTimer.singleShot(0, self.start)
+ @Slot(QProcess.ProcessState)
+ def _on_game_process_changed(self, state: QProcess.ProcessState):
+ self.console.kill_button.setEnabled(state == QProcess.ProcessState.Running)
+ self.console.terminate_button.setEnabled(state == QProcess.ProcessState.Running)
+
@Slot()
- def __proc_log_stdout(self):
+ def _proc_log_stdout(self):
self.console.log_stdout(self.game_process.readAllStandardOutput().data().decode("utf-8", "ignore"))
@Slot()
- def __proc_log_stderr(self):
+ def _proc_log_stderr(self):
self.console.log_stderr(self.game_process.readAllStandardError().data().decode("utf-8", "ignore"))
@Slot()
- def __proc_term(self):
+ def _proc_term(self):
if platform.system() == "Windows":
self.game_process.terminate()
else:
os.kill(self.game_process.processId(), signal.SIGINT)
@Slot()
- def __proc_kill(self):
+ def _proc_kill(self):
if platform.system() == "Windows":
self.game_process.kill()
else:
@@ -251,23 +255,32 @@ def send_message(self, message: BaseModel):
else:
self.logger.error("Can't send message")
- def check_saves_finished(self, exit_code: int):
- self.rgame.signals.widget.update.connect(lambda: self.on_exit(exit_code))
+ def check_saves(self, exit_code: int):
+ # self.rgame.signals.widget.refresh.connect(lambda: self.on_exit(exit_code))
+ self.rgame.signals.widget.refresh.connect(
+ (lambda obj: obj.on_exit(exit_code)).__get__(self)
+ )
state, (dt_local, dt_remote) = self.rgame.save_game_state
if state == SaveGameStatus.LOCAL_NEWER and not self.no_sync_on_exit:
action = CloudSyncDialogResult.UPLOAD
- self.__check_saves_finished(exit_code, action)
+ self.check_saves_finished(exit_code, action)
else:
self.sync_dialog = CloudSyncDialog(self.rgame.igame, dt_local, dt_remote)
- self.sync_dialog.result_ready.connect(lambda a: self.__check_saves_finished(exit_code, a))
+ # self.sync_dialog.result_ready.connect(
+ # lambda a: self.__check_saves_finished(exit_code, a)
+ # )
+ self.sync_dialog.result_ready.connect(
+ (lambda obj, a: obj.check_saves_finished(exit_code, a)).__get__(self)
+ )
self.sync_dialog.open()
@Slot(int, int)
@Slot(int, CloudSyncDialogResult)
- def __check_saves_finished(self, exit_code, action):
+ def check_saves_finished(self, exit_code, action):
if self.sync_dialog is not None:
+ self.sync_dialog.disconnect(self.sync_dialog)
self.sync_dialog.deleteLater()
self.sync_dialog = None
action = CloudSyncDialogResult(action)
@@ -291,7 +304,7 @@ def __process_finished(self, exit_code: int, exit_status: QProcess.ExitStatus):
self.logger.info("Game finished")
if self.rgame.auto_sync_saves:
- self.check_saves_finished(exit_code)
+ self.check_saves(exit_code)
else:
self.on_exit(exit_code)
@@ -482,9 +495,9 @@ def stop(self, sig: int = signal.SIGINT):
if shiboken6.isValid(self.game_process): # pylint: disable=E1101
if self.game_process.state() != QProcess.ProcessState.NotRunning:
if sig == signal.SIGTERM:
- self.__proc_term()
+ self._proc_term()
elif sig == signal.SIGINT:
- self.__proc_kill()
+ self._proc_kill()
self.game_process.waitForFinished()
exit_code = self.game_process.exitCode()
self.game_process.deleteLater()
diff --git a/rare/commands/launcher/console_dialog.py b/rare/commands/launcher/console_dialog.py
index 95fb12e360..587ff74056 100644
--- a/rare/commands/launcher/console_dialog.py
+++ b/rare/commands/launcher/console_dialog.py
@@ -55,13 +55,13 @@ def __init__(self, app_title: str, parent=None):
self.terminate_button = QPushButton(self.tr("Terminate"))
# self.terminate_button.setVisible(platform.system() == "Windows")
button_layout.addWidget(self.terminate_button)
- self.terminate_button.clicked.connect(lambda: self.term.emit())
+ self.terminate_button.clicked.connect(self.term)
self.terminate_button.setEnabled(False)
self.kill_button = QPushButton(self.tr("Kill"))
# self.kill_button.setVisible(platform.system() == "Windows")
button_layout.addWidget(self.kill_button)
- self.kill_button.clicked.connect(lambda: self.kill.emit())
+ self.kill_button.clicked.connect(self.kill)
self.kill_button.setEnabled(False)
layout.addLayout(button_layout)
diff --git a/rare/commands/launcher/lgd_helper.py b/rare/commands/launcher/lgd_helper.py
index ce9cadf566..48ee17888e 100644
--- a/rare/commands/launcher/lgd_helper.py
+++ b/rare/commands/launcher/lgd_helper.py
@@ -10,7 +10,7 @@
from legendary.models.game import LaunchParameters
from PySide6.QtCore import QProcess, QProcessEnvironment
-from rare.models.base_game import RareGameSlim
+from rare.models.game_slim import RareGameSlim
from rare.utils.paths import setup_compat_shaders_dir
logger = getLogger("RareLauncherUtils")
diff --git a/rare/components/dialogs/install/dialog.py b/rare/components/dialogs/install/dialog.py
index d7f952e3c1..592b5c1684 100644
--- a/rare/components/dialogs/install/dialog.py
+++ b/rare/components/dialogs/install/dialog.py
@@ -57,7 +57,7 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal
self.__queue_item: Optional[InstallQueueItemModel] = None
self.selectable = InstallDialogSelective(rgame, parent=self)
- self.selectable.stateChanged.connect(self.__on_option_changed)
+ self.selectable.stateChanged.connect(self._on_option_changed)
self.ui.main_layout.insertRow(
self.ui.main_layout.getWidgetPosition(self.ui.shortcut_label)[0] + 1,
# self.tr("Optional"),
@@ -102,13 +102,13 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal
self.ui.shortcut_label.setDisabled(rgame.is_installed or rgame.is_dlc)
self.ui.shortcut_check.setDisabled(rgame.is_installed or rgame.is_dlc)
self.ui.shortcut_check.setChecked(not rgame.is_installed and self.settings.get_value(app_settings.create_shortcut))
- self.ui.shortcut_check.checkStateChanged.connect(self.__on_option_changed_no_reload)
+ self.ui.shortcut_check.checkStateChanged.connect(self._on_option_changed_no_reload)
self.set_error_labels()
self.ui.platform_combo.addItems(reversed(rgame.platforms))
self.ui.platform_combo.setCurrentIndex(self.ui.platform_combo.findText(options.platform))
- self.ui.platform_combo.currentIndexChanged.connect(self.__on_option_changed)
+ self.ui.platform_combo.currentIndexChanged.connect(self._on_option_changed)
self.ui.platform_combo.currentIndexChanged.connect(self.check_incompatible_platform)
self.ui.platform_combo.currentIndexChanged.connect(self.reset_install_dir)
self.ui.platform_combo.currentTextChanged.connect(self.selectable.update_list)
@@ -122,17 +122,28 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal
self.selectable.click()
self.advanced.ui.max_workers_spin.setValue(self.core.lgd.config.getint("Legendary", "max_workers", fallback=0))
- self.advanced.ui.max_workers_spin.valueChanged.connect(self.__on_option_changed)
+ self.advanced.ui.max_workers_spin.valueChanged.connect(self._on_option_changed)
self.advanced.ui.max_memory_spin.setValue(self.core.lgd.config.getint("Legendary", "max_memory", fallback=0))
- self.advanced.ui.max_memory_spin.valueChanged.connect(self.__on_option_changed)
+ self.advanced.ui.max_memory_spin.valueChanged.connect(self._on_option_changed)
- self.advanced.ui.read_files_check.checkStateChanged.connect(self.__on_option_changed)
- self.advanced.ui.use_signed_urls_check.checkStateChanged.connect(self.__on_option_changed)
- self.advanced.ui.dl_optimizations_check.checkStateChanged.connect(self.__on_option_changed)
- self.advanced.ui.force_download_check.checkStateChanged.connect(self.__on_option_changed)
- self.advanced.ui.ignore_space_check.checkStateChanged.connect(self.__on_option_changed)
- self.advanced.ui.download_only_check.checkStateChanged.connect(self.__on_option_changed_no_reload)
+ self.advanced.ui.read_files_check.setChecked(options.read_files)
+ self.advanced.ui.read_files_check.checkStateChanged.connect(self._on_option_changed)
+
+ self.advanced.ui.use_signed_urls_check.setChecked(options.always_use_signed_urls)
+ self.advanced.ui.use_signed_urls_check.checkStateChanged.connect(self._on_option_changed)
+
+ self.advanced.ui.dl_optimizations_check.setChecked(options.order_opt)
+ self.advanced.ui.dl_optimizations_check.checkStateChanged.connect(self._on_option_changed)
+
+ self.advanced.ui.force_download_check.setChecked(options.force)
+ self.advanced.ui.force_download_check.checkStateChanged.connect(self._on_option_changed)
+
+ self.advanced.ui.ignore_space_check.setChecked(options.ignore_space)
+ self.advanced.ui.ignore_space_check.checkStateChanged.connect(self._on_option_changed)
+
+ self.advanced.ui.download_only_check.setChecked(options.no_install)
+ self.advanced.ui.download_only_check.checkStateChanged.connect(self._on_option_changed_no_reload)
self.reset_install_dir(self.ui.platform_combo.currentIndex())
self.selectable.update_list(self.ui.platform_combo.currentText())
@@ -159,7 +170,7 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal
self.advanced.ui.install_prereqs_label.setEnabled(False)
self.advanced.ui.install_prereqs_check.setEnabled(False)
- self.advanced.ui.install_prereqs_check.checkStateChanged.connect(self.__on_option_changed_no_reload)
+ self.advanced.ui.install_prereqs_check.checkStateChanged.connect(self._on_option_changed_no_reload)
self.advanced.ui.install_prereqs_check.setChecked(self.__options.install_prereqs)
# lk: set object names for CSS properties
@@ -241,13 +252,13 @@ def action_handler(self):
self.get_download_info()
@Slot()
- def __on_option_changed(self):
+ def _on_option_changed(self):
self.options_changed = True
self.accept_button.setEnabled(False)
self.action_button.setEnabled(not self.active())
@Slot(Qt.CheckState)
- def __on_option_changed_no_reload(self, state: Qt.CheckState):
+ def _on_option_changed_no_reload(self, state: Qt.CheckState):
if self.sender() is self.advanced.ui.download_only_check:
self.__options.no_install = state != Qt.CheckState.Unchecked
elif self.sender() is self.ui.shortcut_check:
diff --git a/rare/components/dialogs/install/selective.py b/rare/components/dialogs/install/selective.py
index 8a8f5ff365..cf79b4e4f0 100644
--- a/rare/components/dialogs/install/selective.py
+++ b/rare/components/dialogs/install/selective.py
@@ -75,6 +75,7 @@ def __init__(self, rgame: RareGame, parent=None):
def update_list(self, platform: str):
if self.widget is not None:
+ self.widget.disconnect(self.widget)
self.widget.deleteLater()
self.widget = SelectiveWidget(self.rgame, platform, parent=self)
self.widget.stateChanged.connect(self.stateChanged)
diff --git a/rare/components/dialogs/login/__init__.py b/rare/components/dialogs/login/__init__.py
index c5db7c3ef5..cd52bd6edf 100644
--- a/rare/components/dialogs/login/__init__.py
+++ b/rare/components/dialogs/login/__init__.py
@@ -52,12 +52,12 @@ def __init__(self, args: Namespace, core: LegendaryCore, parent=None):
self.browser_page = BrowserLogin(self.core, self.login_stack)
self.browser_index = self.login_stack.insertWidget(1, self.browser_page)
- self.browser_page.success.connect(self.login_successful)
- self.browser_page.isValid.connect(lambda x: self.ui.next_button.setEnabled(x))
+ self.browser_page.success.connect(self._on_login_successful)
+ self.browser_page.validated.connect(self._on_page_validated)
self.import_page = ImportLogin(self.core, self.login_stack)
self.import_index = self.login_stack.insertWidget(2, self.import_page)
- self.import_page.success.connect(self.login_successful)
- self.import_page.isValid.connect(lambda x: self.ui.next_button.setEnabled(x))
+ self.import_page.success.connect(self._on_login_successful)
+ self.import_page.validated.connect(self._on_page_validated)
self.info_message = {
self.landing_index: self.tr(
@@ -87,14 +87,14 @@ def __init__(self, args: Namespace, core: LegendaryCore, parent=None):
self.ui.next_button.setEnabled(False)
self.ui.back_button.setEnabled(False)
- self.landing_page.ui.login_browser_radio.clicked.connect(lambda: self.ui.next_button.setEnabled(True))
- self.landing_page.ui.login_browser_radio.clicked.connect(self.browser_radio_clicked)
- self.landing_page.ui.login_import_radio.clicked.connect(lambda: self.ui.next_button.setEnabled(True))
- self.landing_page.ui.login_import_radio.clicked.connect(self.import_radio_clicked)
+ self.landing_page.ui.login_browser_radio.clicked.connect(self._on_radio_clicked)
+ self.landing_page.ui.login_browser_radio.clicked.connect(self._on_browser_radio_clicked)
+ self.landing_page.ui.login_import_radio.clicked.connect(self._on_radio_clicked)
+ self.landing_page.ui.login_import_radio.clicked.connect(self._on_import_radio_clicked)
self.ui.exit_button.clicked.connect(self.reject)
- self.ui.back_button.clicked.connect(self.back_clicked)
- self.ui.next_button.clicked.connect(self.next_clicked)
+ self.ui.back_button.clicked.connect(self._on_back_clicked)
+ self.ui.next_button.clicked.connect(self._on_next_clicked)
self.login_stack.setCurrentWidget(self.landing_page)
@@ -110,33 +110,41 @@ def __init__(self, args: Namespace, core: LegendaryCore, parent=None):
self.ui.main_layout.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)
@Slot()
- def browser_radio_clicked(self):
+ def _on_radio_clicked(self):
+ self.ui.next_button.setEnabled(True)
+
+ @Slot(bool)
+ def _on_page_validated(self, valid: bool):
+ self.ui.next_button.setEnabled(valid)
+
+ @Slot()
+ def _on_browser_radio_clicked(self):
self.login_stack.slideInWidget(self.browser_page)
self.ui.info_label.setText(self.info_message[self.browser_index])
self.ui.back_button.setEnabled(True)
self.ui.next_button.setEnabled(False)
@Slot()
- def import_radio_clicked(self):
+ def _on_import_radio_clicked(self):
self.login_stack.slideInWidget(self.import_page)
self.ui.info_label.setText(self.info_message[self.import_index])
self.ui.back_button.setEnabled(True)
self.ui.next_button.setEnabled(self.import_page.is_valid())
@Slot()
- def back_clicked(self):
+ def _on_back_clicked(self):
self.ui.back_button.setEnabled(False)
self.ui.next_button.setEnabled(True)
self.ui.info_label.setText(self.info_message[self.landing_index])
self.login_stack.slideInWidget(self.landing_page)
@Slot()
- def next_clicked(self):
+ def _on_next_clicked(self):
if self.login_stack.currentWidget() is self.landing_page:
if self.landing_page.ui.login_browser_radio.isChecked():
- self.browser_radio_clicked()
+ self._on_browser_radio_clicked()
if self.landing_page.ui.login_import_radio.isChecked():
- self.import_radio_clicked()
+ self._on_import_radio_clicked()
elif self.login_stack.currentWidget() is self.browser_page:
self.browser_page.do_login()
elif self.login_stack.currentWidget() is self.import_page:
@@ -148,7 +156,7 @@ def login(self):
self.open()
@Slot()
- def login_successful(self):
+ def _on_login_successful(self):
try:
if not self.core.login():
raise ValueError("Login failed.")
diff --git a/rare/components/dialogs/login/browser_login.py b/rare/components/dialogs/login/browser_login.py
index 568d3f658c..b2bdc0cff0 100644
--- a/rare/components/dialogs/login/browser_login.py
+++ b/rare/components/dialogs/login/browser_login.py
@@ -1,11 +1,10 @@
import json
-from logging import getLogger
from typing import Tuple
from legendary.utils import webview_login
-from PySide6.QtCore import QProcess, QUrl, Signal, Slot
+from PySide6.QtCore import QProcess, QUrl, Slot
from PySide6.QtGui import QDesktopServices
-from PySide6.QtWidgets import QApplication, QFormLayout, QFrame, QLineEdit
+from PySide6.QtWidgets import QApplication, QFormLayout, QLineEdit
from rare.lgndr.core import LegendaryCore
from rare.lgndr.glue.exception import LgndrException
@@ -14,20 +13,17 @@
from rare.utils.paths import get_rare_executable
from rare.widgets.indicator_edit import IndicatorLineEdit, IndicatorReasonsCommon
+from .login_frame import LoginFrame
-class BrowserLogin(QFrame):
- success = Signal()
- isValid = Signal(bool)
+
+class BrowserLogin(LoginFrame):
def __init__(self, core: LegendaryCore, parent=None):
- super(BrowserLogin, self).__init__(parent=parent)
- self.logger = getLogger(type(self).__name__)
+ super(BrowserLogin, self).__init__(core, parent=parent)
- self.setFrameStyle(QFrame.Shape.StyledPanel)
self.ui = Ui_BrowserLogin()
self.ui.setupUi(self)
- self.core = core
self.login_url = self.core.egs.get_auth_url()
self.auth_edit = IndicatorLineEdit(
@@ -36,23 +32,23 @@ def __init__(self, core: LegendaryCore, parent=None):
self.auth_edit.line_edit.setEchoMode(QLineEdit.EchoMode.Password)
self.ui.link_text.setText(self.login_url)
self.ui.copy_button.setIcon(qta_icon("mdi.content-copy", "fa5.copy"))
- self.ui.copy_button.clicked.connect(self.copy_link)
+ self.ui.copy_button.clicked.connect(self._on_copy_link)
self.ui.form_layout.setWidget(
self.ui.form_layout.getWidgetPosition(self.ui.sid_label)[0],
QFormLayout.ItemRole.FieldRole,
self.auth_edit
)
- self.ui.open_button.clicked.connect(self.open_browser)
- self.auth_edit.textChanged.connect(lambda _: self.isValid.emit(self.is_valid()))
+ self.ui.open_button.clicked.connect(self._on_open_browser)
+ self.auth_edit.textChanged.connect(self._on_input_changed)
@Slot()
- def copy_link(self):
+ def _on_copy_link(self):
clipboard = QApplication.instance().clipboard()
clipboard.setText(self.login_url)
self.ui.status_field.setText(self.tr("Copied to clipboard"))
- def is_valid(self):
+ def is_valid(self) -> bool:
return self.auth_edit.is_valid
@staticmethod
@@ -70,7 +66,7 @@ def sid_edit_callback(text) -> Tuple[bool, str, int]:
else:
return False, text, IndicatorReasonsCommon.VALID
- def do_login(self):
+ def do_login(self) -> None:
self.ui.status_field.setText(self.tr("Logging in..."))
auth_code = self.auth_edit.text()
try:
@@ -84,7 +80,7 @@ def do_login(self):
self.logger.error(e)
@Slot()
- def open_browser(self):
+ def _on_open_browser(self):
if not webview_login.webview_available:
self.logger.warning("You don't have webengine installed, you will need to manually copy the authorizationCode.")
QDesktopServices.openUrl(QUrl(self.login_url))
diff --git a/rare/components/dialogs/login/import_login.py b/rare/components/dialogs/login/import_login.py
index 00294841ba..eee72eba7e 100644
--- a/rare/components/dialogs/login/import_login.py
+++ b/rare/components/dialogs/login/import_login.py
@@ -1,20 +1,19 @@
import os
import platform
from getpass import getuser
-from logging import getLogger
from legendary.lfs.wine_helpers import get_shell_folders, read_registry
-from PySide6.QtCore import Signal, Slot
-from PySide6.QtWidgets import QFileDialog, QFrame
+from PySide6.QtCore import Slot
+from PySide6.QtWidgets import QFileDialog
from rare.lgndr.core import LegendaryCore
from rare.lgndr.glue.exception import LgndrException
from rare.ui.components.dialogs.login.import_login import Ui_ImportLogin
+from .login_frame import LoginFrame
-class ImportLogin(QFrame):
- success = Signal()
- isValid = Signal(bool)
+
+class ImportLogin(LoginFrame):
# FIXME: Use pathspec instead of duplicated code
if platform.system() == "Windows":
@@ -25,15 +24,11 @@ class ImportLogin(QFrame):
found = False
def __init__(self, core: LegendaryCore, parent=None):
- super(ImportLogin, self).__init__(parent=parent)
- self.logger = getLogger(type(self).__name__)
+ super(ImportLogin, self).__init__(core, parent=parent)
- self.setFrameStyle(QFrame.Shape.StyledPanel)
self.ui = Ui_ImportLogin()
self.ui.setupUi(self)
- self.core = core
-
self.text_egl_found = self.tr("Found EGL Program Data. Click 'Next' to import them.")
self.text_egl_notfound = self.tr("Could not find EGL Program Data. ")
@@ -57,8 +52,8 @@ def __init__(self, core: LegendaryCore, parent=None):
else:
self.ui.status_field.setText(self.text_egl_notfound)
- self.ui.prefix_button.clicked.connect(self.prefix_path)
- self.ui.prefix_combo.editTextChanged.connect(lambda _: self.isValid.emit(self.is_valid()))
+ self.ui.prefix_button.clicked.connect(self._on_prefix_path)
+ self.ui.prefix_combo.editTextChanged.connect(self._on_input_changed)
def get_wine_prefixes(self):
possible_prefixes = [
@@ -72,7 +67,7 @@ def get_wine_prefixes(self):
return prefixes
@Slot()
- def prefix_path(self):
+ def _on_prefix_path(self):
prefix_dialog = QFileDialog(self, self.tr("Choose path"), os.path.expanduser("~/"))
prefix_dialog.setFileMode(QFileDialog.FileMode.Directory)
prefix_dialog.setOption(QFileDialog.Option.ShowDirsOnly)
@@ -96,7 +91,7 @@ def is_valid(self) -> bool:
except KeyError:
return False
- def do_login(self):
+ def do_login(self) -> None:
self.ui.status_field.setText(self.tr("Loading..."))
if os.name != "nt":
self.logger.info("Using EGL appdata path at %s", {self.egl_appdata})
diff --git a/rare/components/dialogs/login/login_frame.py b/rare/components/dialogs/login/login_frame.py
new file mode 100644
index 0000000000..2325efaf6b
--- /dev/null
+++ b/rare/components/dialogs/login/login_frame.py
@@ -0,0 +1,33 @@
+from abc import abstractmethod
+from logging import getLogger
+
+from PySide6.QtCore import Signal, Slot
+from PySide6.QtWidgets import QFrame
+
+from rare.lgndr.core import LegendaryCore
+
+
+class LoginFrame(QFrame):
+ success = Signal()
+ validated = Signal(bool)
+
+ def __init__(self, core: LegendaryCore, parent=None):
+ super(LoginFrame, self).__init__(parent=parent)
+
+ self.logger = getLogger(type(self).__name__)
+ self.core = core
+
+ self.setFrameStyle(QFrame.Shape.StyledPanel)
+
+ @abstractmethod
+ def is_valid(self) -> bool:
+ pass
+
+ @Slot()
+ def _on_input_changed(self):
+ self.validated.emit(self.is_valid())
+
+ @abstractmethod
+ def do_login(self) -> None:
+ pass
+
diff --git a/rare/components/main_window.py b/rare/components/main_window.py
index 41fe9ee553..2f9a85c5a5 100644
--- a/rare/components/main_window.py
+++ b/rare/components/main_window.py
@@ -118,7 +118,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.tray_icon: TrayIcon = TrayIcon(self.settings, self.rcore, self)
self.tray_icon.exit_app.connect(self.__on_exit_app)
self.tray_icon.show_app.connect(self.show)
- self.tray_icon.activated.connect(lambda r: self.toggle() if r == QSystemTrayIcon.ActivationReason.DoubleClick else None)
+ self.tray_icon.activated.connect(self._on_tray_icon_activated)
# enable kinetic scrolling
for scroll_area in self.findChildren(QScrollArea):
@@ -132,6 +132,11 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
for combo_box in scroll_area.findChildren(QComboBox):
combo_box.wheelEvent = lambda e: e.ignore()
+ @Slot(QSystemTrayIcon.ActivationReason)
+ def _on_tray_icon_activated(self, reason: QSystemTrayIcon.ActivationReason):
+ if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
+ self.toggle()
+
def center_window(self):
# get the margins of the decorated window
margins = self.windowHandle().frameMargins()
diff --git a/rare/components/tabs/__init__.py b/rare/components/tabs/__init__.py
index 6af53e6d66..a8b97dcb97 100644
--- a/rare/components/tabs/__init__.py
+++ b/rare/components/tabs/__init__.py
@@ -62,11 +62,11 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent):
# Settings Tab
self.settings_tab = SettingsTab(settings, rcore, self)
self.settings_index = self.addTab(self.settings_tab, qta_icon("fa.gear", "fa6s.gear"), self.tr("Settings"))
- self.settings_tab.about.update_available_ready.connect(lambda: self.main_bar.setTabText(self.settings_index, self.tr("Settings (!)")))
+ self.settings_tab.update_available.connect(self._on_update_available)
# Account Tab
self.account_widget = AccountWidget(self.signals, self.core, self)
- self.account_widget.exit_app.connect(self.__on_exit_app)
+ self.account_widget.exit_app.connect(self._on_exit_app)
account_action = QWidgetAction(self)
account_action.setDefaultWidget(self.account_widget)
self.account_menu = QMenu(self)
@@ -80,12 +80,12 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent):
self.tabBarClicked.connect(self.mouse_clicked)
# shortcuts
- QShortcut("Alt+1", self).activated.connect(lambda: self.setCurrentIndex(self.games_index))
+ QShortcut("Alt+1", self).activated.connect(self._on_shortcut_activated_games)
if not self.args.offline:
- QShortcut("Alt+2", self).activated.connect(lambda: self.setCurrentIndex(self.downloads_index))
- QShortcut("Alt+3", self).activated.connect(lambda: self.setCurrentIndex(self.store_index))
- QShortcut("Alt+4", self).activated.connect(lambda: self.setCurrentIndex(self.integrations_index))
- QShortcut("Alt+5", self).activated.connect(lambda: self.setCurrentIndex(self.settings_index))
+ QShortcut("Alt+2", self).activated.connect(self._on_shortcut_activated_downloads)
+ QShortcut("Alt+3", self).activated.connect(self._on_shortcut_activated_store)
+ QShortcut("Alt+4", self).activated.connect(self._on_shortcut_activated_integrations)
+ QShortcut("Alt+5", self).activated.connect(self._on_shortcut_activated_settings)
self.setCurrentIndex(self.games_index)
@@ -104,6 +104,30 @@ def eventFilter(self, w: QObject, e: QEvent) -> bool:
return True
return False
+ @Slot()
+ def _on_shortcut_activated_games(self):
+ self.setCurrentIndex(self.games_index)
+
+ @Slot()
+ def _on_shortcut_activated_downloads(self):
+ self.setCurrentIndex(self.downloads_index)
+
+ @Slot()
+ def _on_shortcut_activated_store(self):
+ self.setCurrentIndex(self.store_index)
+
+ @Slot()
+ def _on_shortcut_activated_integrations(self):
+ self.setCurrentIndex(self.integrations_index)
+
+ @Slot()
+ def _on_shortcut_activated_settings(self):
+ self.setCurrentIndex(self.settings_index)
+
+ @Slot()
+ def _on_update_available(self):
+ self.main_bar.setTabText(self.settings_index, self.tr("Settings (!)"))
+
@Slot()
@Slot(str)
def show_import(self, app_name: str = None):
@@ -144,7 +168,7 @@ def resizeEvent(self, event):
super(MainTabWidget, self).resizeEvent(event)
@Slot(int)
- def __on_exit_app(self, exit_code: int):
+ def _on_exit_app(self, exit_code: int):
# FIXME: Don't allow logging out if there are active downloads
if self.downloads_tab.is_download_active:
QMessageBox.warning(
diff --git a/rare/components/tabs/account/__init__.py b/rare/components/tabs/account/__init__.py
index e85337ed37..94326552b4 100644
--- a/rare/components/tabs/account/__init__.py
+++ b/rare/components/tabs/account/__init__.py
@@ -26,13 +26,12 @@ def __init__(self, signals: GlobalSignals, core: LegendaryCore, parent):
qta_icon("fa.external-link", "fa5s.external-link-alt"),
self.tr("Account settings"),
)
- self.open_browser.clicked.connect(
- lambda: webbrowser.open("https://www.epicgames.com/account/personal?productName=epicgames")
- )
+ self.open_browser.clicked.connect(self._on_browser_clicked)
+
self.logout_button = QPushButton(self.tr("Logout"), parent=self)
- self.logout_button.clicked.connect(self.__on_logout)
+ self.logout_button.clicked.connect(self._on_logout)
self.quit_button = QPushButton(self.tr("Quit"), parent=self)
- self.quit_button.clicked.connect(self.__on_quit)
+ self.quit_button.clicked.connect(self._on_quit)
layout = QVBoxLayout(self)
layout.addWidget(QLabel(self.tr("Logged in as {}").format(username)))
@@ -43,9 +42,13 @@ def __init__(self, signals: GlobalSignals, core: LegendaryCore, parent):
layout.addWidget(self.quit_button)
@Slot()
- def __on_quit(self):
+ def _on_browser_clicked(self):
+ webbrowser.open("https://www.epicgames.com/account/personal?productName=epicgames")
+
+ @Slot()
+ def _on_quit(self):
self.exit_app.emit(ExitCodes.EXIT)
@Slot()
- def __on_logout(self):
+ def _on_logout(self):
self.exit_app.emit(ExitCodes.LOGOUT)
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index a42e8c7adf..12857903a3 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -34,8 +34,6 @@
from .groups import QueueGroup, UpdateGroup
from .thread import DlResultCode, DlResultModel, DlThread
-logger = getLogger("Download")
-
def get_time(seconds: Union[int, float]) -> str:
return str(datetime.timedelta(seconds=seconds))
@@ -47,6 +45,8 @@ class DownloadsTab(QWidget):
def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
super(DownloadsTab, self).__init__(parent=parent)
+ self.logger = getLogger(type(self).__name__)
+
self.settings = settings
self.rcore = rcore
self.core = rcore.core()
@@ -158,7 +158,7 @@ def __on_queue_force(self, item: InstallQueueItemModel):
self.stop_download()
self.__forced_item = item
else:
- self.__start_download(item)
+ self.start_download(item)
def stop_download(self, omit_queue=False):
"""
@@ -181,17 +181,26 @@ def stop_download(self, omit_queue=False):
def __refresh_download(self, item: InstallQueueItemModel):
worker = InstallInfoWorker(self.core, item.options)
- worker.signals.result.connect(lambda d: self.__start_download(InstallQueueItemModel(options=item.options, download=d)))
+
+ worker.signals.result.connect(
+ (lambda obj, d: obj.start_download(
+ InstallQueueItemModel(options=item.options, download=d))).__get__(self)
+ )
worker.signals.failed.connect(
- lambda m: logger.error(f"Failed to refresh download for {item.options.app_name} with error: {m}")
+ (lambda obj, m: obj.logger.error(
+ f"Failed to refresh download for {item.options.app_name} with error: {m}")).__get__(self)
+ )
+ worker.signals.finished.connect(
+ (lambda obj, m: obj.logger.info(
+ f"Download refresh worker finished for {item.options.app_name}")).__get__(self)
)
- worker.signals.finished.connect(lambda: logger.info(f"Download refresh worker finished for {item.options.app_name}"))
+
QThreadPool.globalInstance().start(worker)
- def __start_download(self, item: InstallQueueItemModel):
+ def start_download(self, item: InstallQueueItemModel):
rgame = self.rcore.get_game(item.options.app_name)
if not rgame.state == RareGame.State.DOWNLOADING:
- logger.error(
+ self.logger.error(
f"Can't start download {item.options.app_name} due to incompatible state {RareGame.State(rgame.state).name}"
)
# lk: invalidate the queue item in case the game was uninstalled
@@ -228,7 +237,7 @@ def __requeue_download(self, item: InstallQueueItemModel):
rgame = self.rcore.get_game(item.options.app_name)
rgame.state = RareGame.State.DOWNLOADING
self.queue_group.push_front(item, rgame.igame)
- logger.info(f"Re-queued download for {rgame.app_name} ({rgame.app_title})")
+ self.logger.info(f"Re-queued download for {rgame.app_name} ({rgame.app_title})")
@Slot(DlResultModel)
def __on_download_result(self, result: DlResultModel):
@@ -237,7 +246,7 @@ def __on_download_result(self, result: DlResultModel):
self.__thread.deleteLater()
if result.code == DlResultCode.FINISHED:
- logger.info(f"Download finished: {result.options.app_name}")
+ self.logger.info(f"Download finished: {result.options.app_name}")
if result.shortcut and desktop_links_supported():
if not create_desktop_link(
app_name=result.options.app_name,
@@ -246,9 +255,9 @@ def __on_download_result(self, result: DlResultModel):
link_type="desktop",
):
# maybe add it to download summary, to show in finished downloads
- logger.error(f"Failed to create desktop link on {platform.system()}")
+ self.logger.error(f"Failed to create desktop link on {platform.system()}")
else:
- logger.info(f"Created desktop link {result.folder_name} for {result.app_title}")
+ self.logger.info(f"Created desktop link {result.folder_name} for {result.app_title}")
self.signals.application.notify.emit(
self.tr("Downloads"),
@@ -259,7 +268,7 @@ def __on_download_result(self, result: DlResultModel):
self.updates_group.set_widget_enabled(result.options.app_name, True)
elif result.code == DlResultCode.ERROR:
- logger.error(f"Download error: {result.options.app_name} ({result.message})")
+ self.logger.error(f"Download error: {result.options.app_name} ({result.message})")
QMessageBox.warning(
self,
self.tr("Error - {}").format(result.app_title),
@@ -268,7 +277,7 @@ def __on_download_result(self, result: DlResultModel):
)
elif result.code == DlResultCode.STOPPED:
- logger.info(f"Download stopped: {result.options.app_name}")
+ self.logger.info(f"Download stopped: {result.options.app_name}")
if not self.__omit_requeue:
self.__requeue_download(InstallQueueItemModel(options=result.options))
else:
@@ -279,9 +288,9 @@ def __on_download_result(self, result: DlResultModel):
self.updates_group.set_widget_enabled(result.options.app_name, True)
if result.code == DlResultCode.FINISHED and self.queue_group.count():
- self.__start_download(self.queue_group.pop_front())
+ self.start_download(self.queue_group.pop_front())
elif result.code == DlResultCode.STOPPED and self.__forced_item:
- self.__start_download(self.__forced_item)
+ self.start_download(self.__forced_item)
self.__forced_item = None
else:
self.__reset_download()
@@ -321,7 +330,7 @@ def __on_install_dialog_closed(self, item: InstallQueueItemModel):
if item:
# lk: start update only if there is no other active thread and there is no queue
if self.__thread is None and not self.queue_group.count():
- self.__start_download(item)
+ self.start_download(item)
else:
rgame = self.rcore.get_game(item.options.app_name)
self.queue_group.push_back(item, rgame.igame)
diff --git a/rare/components/tabs/downloads/groups.py b/rare/components/tabs/downloads/groups.py
index ebbd0700ca..6fd185e993 100644
--- a/rare/components/tabs/downloads/groups.py
+++ b/rare/components/tabs/downloads/groups.py
@@ -66,6 +66,7 @@ def append(self, game: Game, igame: InstalledGame):
widget: UpdateWidget = self.__find_widget(game.app_name)
if widget is not None:
self.__container.layout().removeWidget(widget)
+ widget.disconnect(widget)
widget.deleteLater()
widget = UpdateWidget(self.imgmgr, game, igame, parent=self.__container)
widget.destroyed.connect(self.__update_group)
@@ -75,6 +76,7 @@ def append(self, game: Game, igame: InstalledGame):
def remove(self, app_name: str):
widget: UpdateWidget = self.__find_widget(app_name)
self.__container.layout().removeWidget(widget)
+ widget.disconnect(widget)
widget.deleteLater()
def set_widget_enabled(self, app_name: str, enabled: bool):
@@ -191,6 +193,7 @@ def __remove(self, app_name: str):
self.__queue.remove(app_name)
widget: QueueWidget = self.__find_widget(app_name)
self.__container.layout().removeWidget(widget)
+ widget.disconnect(widget)
widget.deleteLater()
@Slot(str)
diff --git a/rare/components/tabs/downloads/thread.py b/rare/components/tabs/downloads/thread.py
index ec797dd931..3a4386c40b 100644
--- a/rare/components/tabs/downloads/thread.py
+++ b/rare/components/tabs/downloads/thread.py
@@ -58,16 +58,17 @@ def __init__(
self.rgame = rgame
self.debug = debug
- def __finish(self, result):
+ def _finish(self, result):
if result.code == DlResultCode.FINISHED and not result.options.no_install:
self.rgame.set_installed(True)
self.rgame.state = RareGame.State.IDLE
self.rgame.signals.progress.finish.emit(result.code != DlResultCode.FINISHED)
self.result.emit(result)
+ self.quit()
- def __status_callback(self, status: UIUpdate):
+ def _status_callback(self, status: UIUpdate):
self.progress.emit(status, self.dl_size)
- self.rgame.signals.progress.update.emit(int(status.progress))
+ self.rgame.signals.progress.refresh.emit(int(status.progress))
def run(self):
cli = LegendaryCLI(self.core)
@@ -115,7 +116,7 @@ def chunk_url_sign_thread():
time.sleep(1)
while self.item.download.dlm.is_alive():
try:
- self.__status_callback(self.item.download.dlm.status_queue.get(timeout=1.0))
+ self._status_callback(self.item.download.dlm.status_queue.get(timeout=1.0))
except queue.Empty:
pass
if self.dlm_signals.update:
@@ -194,7 +195,7 @@ def chunk_url_sign_thread():
sign_thread.stop = True
ticket_thread.join()
sign_thread.join()
- self.__finish(result)
+ self._finish(result)
def _handle_postinstall(self, postinstall, igame):
self.logger.info("This game lists the following prerequisites to be installed:")
@@ -209,7 +210,8 @@ def _handle_postinstall(self, postinstall, igame):
proc = QProcess(self)
proc.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels)
proc.readyReadStandardOutput.connect(
- lambda: self.logger.debug(str(proc.readAllStandardOutput().data(), "utf-8", "ignore"))
+ (lambda obj: obj.logger.debug(
+ str(proc.readAllStandardOutput().data(), "utf-8", "ignore"))).__get__(self)
)
proc.setProgram(fullpath)
proc.setArguments(postinstall.get("args", "").split(" "))
diff --git a/rare/components/tabs/downloads/widgets.py b/rare/components/tabs/downloads/widgets.py
index 00f67f0987..93f32f4f80 100644
--- a/rare/components/tabs/downloads/widgets.py
+++ b/rare/components/tabs/downloads/widgets.py
@@ -90,10 +90,18 @@ def __init__(self, imgmgr: ImageManager, game: Game, igame: InstalledGame, paren
self.info_widget = QueueInfoWidget(imgmgr, game, igame, parent=self)
self.ui.info_layout.addWidget(self.info_widget)
- self.ui.update_button.clicked.connect(lambda: self.update_game(True))
- self.ui.settings_button.clicked.connect(lambda: self.update_game(False))
+ self.ui.update_button.clicked.connect(self._on_update_clicked)
+ self.ui.settings_button.clicked.connect(self._on_settings_clicked)
- def update_game(self, auto: bool):
+ @Slot()
+ def _on_update_clicked(self):
+ self._update_game(True)
+
+ @Slot()
+ def _on_settings_clicked(self):
+ self._update_game(False)
+
+ def _update_game(self, auto: bool):
self.ui.update_button.setDisabled(True)
self.ui.settings_button.setDisabled(True)
self.enqueue.emit(InstallOptionsModel(
@@ -126,6 +134,7 @@ def __init__(
self, core: LegendaryCore, imgmgr: ImageManager, item: InstallQueueItemModel, old_igame: InstalledGame, parent=None
):
super(QueueWidget, self).__init__(parent=parent)
+ self.logger = getLogger(type(self).__name__)
self.ui = Ui_QueueBaseWidget()
self.ui.setupUi(self)
# lk: setObjectName has to be after `setupUi` because it is also set in that function
@@ -136,10 +145,14 @@ def __init__(
worker = InstallInfoWorker(core, item.options)
worker.signals.result.connect(self.__update_info)
worker.signals.failed.connect(
- lambda m: logger.error(f"Failed to requeue download for {item.options.app_name} with error: {m}")
+ (lambda obj, m: obj.logger.error(
+ f"Failed to requeue download for {item.options.app_name} with error: {m}")).__get__(self)
+ )
+ worker.signals.failed.connect((lambda obj, m: obj.remove.emit(item.options.app_name)).__get__(self))
+ worker.signals.finished.connect(
+ (lambda obj: obj.logger.error(
+ f"Download requeue worker finished for {item.options.app_name}")).__get__(self)
)
- worker.signals.failed.connect(lambda m: self.remove.emit(item.options.app_name))
- worker.signals.finished.connect(lambda: logger.info(f"Download requeue worker finished for {item.options.app_name}"))
QThreadPool.globalInstance().start(worker)
self.info_widget = QueueInfoWidget(imgmgr, None, None, None, old_igame, parent=self)
else:
@@ -158,13 +171,29 @@ def __init__(
self.item = item
self.ui.move_up_button.setIcon(qta_icon("fa.arrow-up", "fa5s.arrow-up"))
- self.ui.move_up_button.clicked.connect(lambda: self.move_up.emit(self.item.options.app_name))
+ self.ui.move_up_button.clicked.connect(self._on_move_up)
self.ui.move_down_button.setIcon(qta_icon("fa.arrow-down", "fa5s.arrow-down"))
- self.ui.move_down_button.clicked.connect(lambda: self.move_down.emit(self.item.options.app_name))
+ self.ui.move_down_button.clicked.connect(self._on_move_down)
+
+ self.ui.remove_button.clicked.connect(self._on_remove)
+ self.ui.force_button.clicked.connect(self._on_force)
+
+ @Slot()
+ def _on_move_up(self):
+ self.move_up.emit(self.item.options.app_name)
+
+ @Slot()
+ def _on_move_down(self):
+ self.move_down.emit(self.item.options.app_name)
+
+ @Slot()
+ def _on_remove(self):
+ self.remove.emit(self.item.options.app_name)
- self.ui.remove_button.clicked.connect(lambda: self.remove.emit(self.item.options.app_name))
- self.ui.force_button.clicked.connect(lambda: self.force.emit(self.item))
+ @Slot()
+ def _on_force(self):
+ self.force.emit(self.item)
@Slot(InstallDownloadModel)
def __update_info(self, download: InstallDownloadModel):
diff --git a/rare/components/tabs/integrations/egl_sync_group.py b/rare/components/tabs/integrations/egl_sync_group.py
index 6954e67130..2f467b4790 100644
--- a/rare/components/tabs/integrations/egl_sync_group.py
+++ b/rare/components/tabs/integrations/egl_sync_group.py
@@ -265,21 +265,32 @@ def __init__(self, rcore: RareCore, parent=None):
self.ui.setupUi(self)
self.ui.list.setFrameShape(QFrame.Shape.NoFrame)
- self.ui.list.itemDoubleClicked.connect(
- lambda item: item.setCheckState(Qt.CheckState.Unchecked)
- if item.checkState() != Qt.CheckState.Unchecked
- else item.setCheckState(Qt.CheckState.Checked)
- )
- self.ui.list.itemChanged.connect(self.has_selected)
+ self.ui.list.itemDoubleClicked.connect(self._on_item_double_clicked)
+ self.ui.list.itemChanged.connect(self._has_selected)
- self.ui.select_all_button.clicked.connect(lambda: self.mark(Qt.CheckState.Checked))
- self.ui.select_none_button.clicked.connect(lambda: self.mark(Qt.CheckState.Unchecked))
+ self.ui.select_all_button.clicked.connect(self._on_mark_all)
+ self.ui.select_none_button.clicked.connect(self._on_mark_none)
self.ui.action_button.clicked.connect(self.action)
self.action_errors.connect(self.show_errors)
- def has_selected(self):
+ @Slot()
+ def _on_mark_all(self):
+ self.mark(Qt.CheckState.Checked)
+
+ @Slot()
+ def _on_mark_none(self):
+ self.mark(Qt.CheckState.Unchecked)
+
+ @Slot(QListWidgetItem)
+ def _on_item_double_clicked(self, item: QListWidgetItem):
+ if item.checkState() != Qt.CheckState.Unchecked:
+ item.setCheckState(Qt.CheckState.Unchecked)
+ else:
+ item.setCheckState(Qt.CheckState.Checked)
+
+ def _has_selected(self):
for item in self.items:
if item.is_checked():
self.ui.action_button.setEnabled(True)
diff --git a/rare/components/tabs/integrations/eos_group.py b/rare/components/tabs/integrations/eos_group.py
index b9f500ba42..8c7940c7b9 100644
--- a/rare/components/tabs/integrations/eos_group.py
+++ b/rare/components/tabs/integrations/eos_group.py
@@ -37,19 +37,23 @@
logger = getLogger("EpicOverlay")
+class CheckForUpdateWorkerSignals(QObject):
+ update_available = Signal(bool)
+
+
class CheckForUpdateWorker(QRunnable):
- class CheckForUpdateSignals(QObject):
- update_available = Signal(bool)
def __init__(self, core: LegendaryCore):
super(CheckForUpdateWorker, self).__init__()
- self.signals = self.CheckForUpdateSignals()
+ self.signals = CheckForUpdateWorkerSignals()
self.setAutoDelete(True)
self.core = core
def run(self) -> None:
self.core.check_for_overlay_updates()
self.signals.update_available.emit(self.core.overlay_update_available)
+ self.signals.disconnect(self.signals)
+ self.signals.deleteLater()
class EosPrefixWidget(QFrame):
@@ -199,7 +203,7 @@ def __init__(self, rcore: RareCore, parent=None):
self.ui.info_layout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.version)
self.ui.info_layout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.install_path)
- self.overlay.signals.widget.update.connect(self.update_state)
+ self.overlay.signals.widget.refresh.connect(self.update_state)
self.overlay.signals.game.installed.connect(self.install_finished)
self.overlay.signals.game.uninstalled.connect(self.uninstall_finished)
@@ -222,6 +226,7 @@ def hideEvent(self, e: QHideEvent, /):
if e.spontaneous():
return super().hideEvent(e)
for widget in self.findChildren(EosPrefixWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
+ widget.disconnect(widget)
widget.deleteLater()
return super().hideEvent(e)
@@ -249,6 +254,7 @@ def update_state(self):
def update_prefixes(self):
for widget in self.findChildren(EosPrefixWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
+ widget.disconnect(widget)
widget.deleteLater()
if platform.system() != "Windows":
diff --git a/rare/components/tabs/integrations/import_group.py b/rare/components/tabs/integrations/import_group.py
index 29a7c662ab..7d9ee0f8c1 100644
--- a/rare/components/tabs/integrations/import_group.py
+++ b/rare/components/tabs/integrations/import_group.py
@@ -67,10 +67,12 @@ class ImportedGame:
message: Optional[str] = None
+class ImportWorkerSignals(QObject):
+ progress = Signal(ImportedGame, int)
+ result = Signal(list)
+
+
class ImportWorker(QRunnable):
- class Signals(QObject):
- progress = Signal(ImportedGame, int)
- result = Signal(list)
def __init__(
self,
@@ -84,7 +86,7 @@ def __init__(
):
super(ImportWorker, self).__init__()
self.setAutoDelete(True)
- self.signals = ImportWorker.Signals()
+ self.signals = ImportWorkerSignals()
self.core = core
self.path = Path(path)
@@ -110,6 +112,8 @@ def run(self) -> None:
result_list.append(result)
self.signals.progress.emit(result, 100)
self.signals.result.emit(result_list)
+ self.signals.disconnect(self.signals)
+ self.signals.deleteLater()
def _try_import(self, path: Path, app_name: str = None) -> ImportedGame:
result = ImportedGame(ImportResult.ERROR)
@@ -188,13 +192,13 @@ def __init__(self, rcore: RareCore, parent=None):
self.app_name_edit,
)
- self.ui.import_folder_check.checkStateChanged.connect(self.import_folder_changed)
+ self.ui.import_folder_check.checkStateChanged.connect(self._on_import_folder_changed)
self.ui.import_dlcs_check.setEnabled(False)
- self.ui.import_dlcs_check.checkStateChanged.connect(self.import_dlcs_changed)
+ self.ui.import_dlcs_check.checkStateChanged.connect(self._on_import_dlcs_changed)
self.ui.import_button_label.setText("")
self.ui.import_button.setEnabled(False)
- self.ui.import_button.clicked.connect(lambda: self._import(self.path_edit.text()))
+ self.ui.import_button.clicked.connect(self._on_import_clicked)
self.button_info_stack = QStackedWidget(self)
self.button_info_stack.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
@@ -276,7 +280,7 @@ def _app_name_changed(self, app_name: str):
self.ui.import_button.setEnabled(not bool(self.worker) and (self.app_name_edit.is_valid and self.path_edit.is_valid))
@Slot(Qt.CheckState)
- def import_folder_changed(self, state: Qt.CheckState):
+ def _on_import_folder_changed(self, state: Qt.CheckState):
self.app_name_edit.setEnabled(not state)
self.ui.platform_combo.setEnabled(not state)
self.ui.platform_combo.setToolTip(
@@ -300,11 +304,15 @@ def import_folder_changed(self, state: Qt.CheckState):
)
@Slot(Qt.CheckState)
- def import_dlcs_changed(self, state: Qt.CheckState):
+ def _on_import_dlcs_changed(self, state: Qt.CheckState):
self.ui.import_button.setEnabled(
not bool(self.worker) and (state != Qt.CheckState.Unchecked or self.app_name_edit.is_valid)
)
+ @Slot()
+ def _on_import_clicked(self):
+ self._import(self.path_edit.text())
+
@Slot(str)
def _import(self, path: Optional[str] = None):
self.ui.import_button.setDisabled(True)
diff --git a/rare/components/tabs/integrations/ubisoft_group.py b/rare/components/tabs/integrations/ubisoft_group.py
index 2a3f8b587f..fd772dd26d 100644
--- a/rare/components/tabs/integrations/ubisoft_group.py
+++ b/rare/components/tabs/integrations/ubisoft_group.py
@@ -25,7 +25,7 @@
class UbiGetInfoWorkerSignals(QObject):
- worker_finished = Signal(set, set, str)
+ result = Signal(set, set, str)
class UbiGetInfoWorker(Worker):
@@ -45,7 +45,7 @@ def run_real(self) -> None:
ubi_account_id = ext_auth["externalAuthId"]
break
else:
- self.signals.worker_finished.emit(set(), set(), "")
+ self.signals.result.emit(set(), set(), "")
return
with timelogger(self.logger, "Request uplay codes"):
@@ -62,10 +62,10 @@ def run_real(self) -> None:
self.core.lgd.entitlements = entitlements
entitlements = {i["entitlementName"] for i in entitlements}
- self.signals.worker_finished.emit(redeemed, entitlements, ubi_account_id)
+ self.signals.result.emit(redeemed, entitlements, ubi_account_id)
except Exception as e:
self.logger.error(e)
- self.signals.worker_finished.emit(set(), set(), "error")
+ self.signals.result.emit(set(), set(), "error")
class UbiConnectWorkerSignals(QObject):
@@ -138,10 +138,10 @@ def activate(self):
worker = UbiConnectWorker(self.core, None, None)
else:
worker = UbiConnectWorker(self.core, self.ubi_account_id, self.game.partner_link_id)
- worker.signals.linked.connect(self.worker_finished)
+ worker.signals.linked.connect(self._on_linked)
QThreadPool.globalInstance().start(worker)
- def worker_finished(self, error):
+ def _on_linked(self, error):
if not error:
self.redeem_indicator.setPixmap(
qta_icon("fa.check-circle-o", "fa5.check-circle", color="green").pixmap(QSize(20, 20))
@@ -173,7 +173,7 @@ def __init__(self, rcore: RareCore, parent=None):
self.info_label.setText(self.tr("Getting information about your redeemable Ubisoft games."))
self.link_button = QPushButton(self.tr("Link Ubisoft acccount"), parent=self)
self.link_button.setMinimumWidth(140)
- self.link_button.clicked.connect(lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft"))
+ self.link_button.clicked.connect(self._on_link_clicked)
self.link_button.setEnabled(False)
self.loading_widget = LoadingWidget(self)
@@ -187,6 +187,10 @@ def __init__(self, rcore: RareCore, parent=None):
layout.addLayout(header_layout)
layout.addWidget(self.loading_widget)
+ @Slot()
+ def _on_link_clicked(self):
+ webbrowser.open("https://www.epicgames.com/id/link/ubisoft")
+
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
@@ -195,16 +199,17 @@ def showEvent(self, a0: QShowEvent) -> None:
return super().showEvent(a0)
for widget in self.findChildren(UbiLinkWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
+ widget.disconnect(widget)
widget.deleteLater()
self.loading_widget.start()
self.worker = UbiGetInfoWorker(self.core)
- self.worker.signals.worker_finished.connect(self.show_ubi_games)
+ self.worker.signals.result.connect(self._on_result)
self.thread_pool.start(self.worker)
return super().showEvent(a0)
@Slot(set, set, str)
- def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str):
+ def _on_result(self, redeemed: set, entitlements: set, ubi_account_id: str):
self.worker = None
self.loading_widget.stop()
if not redeemed and ubi_account_id != "error":
diff --git a/rare/components/tabs/library/__init__.py b/rare/components/tabs/library/__init__.py
index 1d7ebea27e..5d515d1a41 100644
--- a/rare/components/tabs/library/__init__.py
+++ b/rare/components/tabs/library/__init__.py
@@ -42,9 +42,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
library_page_right_layout.addWidget(self.head_bar)
self.details_page = GameDetailsTabs(settings, rcore, self)
- self.details_page.back_clicked.connect(lambda: self.setCurrentWidget(self.library_page))
- # Update visibility of hidden games
- self.details_page.back_clicked.connect(lambda: self.filter_games(self.head_bar.current_filter()))
+ self.details_page.back_clicked.connect(self._on_back_clicked)
self.details_page.import_clicked.connect(self.import_clicked)
self.addWidget(self.details_page)
@@ -59,11 +57,11 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.library_controller = LibraryWidgetController(rcore, library_view, self.view_scroll)
self.head_bar.search_bar.textChanged.connect(self.search_games)
- self.head_bar.search_bar.textChanged.connect(self.scroll_to_top)
+ self.head_bar.search_bar.textChanged.connect(self._scroll_to_top)
self.head_bar.filterChanged.connect(self.filter_games)
- self.head_bar.filterChanged.connect(self.scroll_to_top)
+ self.head_bar.filterChanged.connect(self._scroll_to_top)
self.head_bar.orderChanged.connect(self.order_games)
- self.head_bar.orderChanged.connect(self.scroll_to_top)
+ self.head_bar.orderChanged.connect(self._scroll_to_top)
# signals
self.signals.game.installed.connect(self.update_count_games_label)
@@ -79,7 +77,12 @@ def showEvent(self, a0: QShowEvent):
return super().showEvent(a0)
@Slot()
- def scroll_to_top(self):
+ def _on_back_clicked(self):
+ self.filter_games(self.head_bar.current_filter())
+ self.setCurrentWidget(self.library_page)
+
+ @Slot()
+ def _scroll_to_top(self):
self.view_scroll.verticalScrollBar().setSliderPosition(self.view_scroll.verticalScrollBar().minimum())
@Slot()
diff --git a/rare/components/tabs/library/details/cloud_saves.py b/rare/components/tabs/library/details/cloud_saves.py
index 694e1fec53..2dfe6d326a 100644
--- a/rare/components/tabs/library/details/cloud_saves.py
+++ b/rare/components/tabs/library/details/cloud_saves.py
@@ -234,12 +234,12 @@ def __update_widget(self):
def update_game(self, rgame: RareGame):
if self.rgame:
- self.rgame.signals.widget.update.disconnect(self.__update_widget)
+ self.rgame.signals.widget.refresh.disconnect(self.__update_widget)
self.rgame = rgame
self.save_path_spec = PathSpec(self.core, self.rgame.igame).resolve_egl_path_vars(self.rgame.raw_save_path)
self.set_title.emit(rgame.app_title)
- rgame.signals.widget.update.connect(self.__update_widget)
+ rgame.signals.widget.refresh.connect(self.__update_widget)
self.__update_widget()
diff --git a/rare/components/tabs/library/details/details.py b/rare/components/tabs/library/details/details.py
index 32568d2609..1b56f66d36 100644
--- a/rare/components/tabs/library/details/details.py
+++ b/rare/components/tabs/library/details/details.py
@@ -395,6 +395,7 @@ def __update_widget(self):
self.ui.actions_stack.setCurrentWidget(self.ui.uninstalled_page)
for w in self.ui.tags_group.findChildren(GameTagCheckBox, options=Qt.FindChildOption.FindDirectChildrenOnly):
+ w.disconnect(w)
w.deleteLater()
for tag in self.rcore.game_tags:
@@ -417,11 +418,11 @@ def update_game(self, rgame: RareGame):
worker.signals.progress.disconnect(self.__on_move_progress)
except TypeError as e:
logger.warning(f"{self.rgame.app_name} move worker: {e}")
- self.rgame.signals.widget.update.disconnect(self.__update_widget)
+ self.rgame.signals.widget.refresh.disconnect(self.__update_widget)
self.rgame = None
- rgame.signals.widget.update.connect(self.__update_widget)
+ rgame.signals.widget.refresh.connect(self.__update_widget)
if (worker := rgame.get_worker()) is not None:
if isinstance(worker, VerifyWorker):
worker.signals.progress.connect(self.__on_verify_progress)
@@ -445,6 +446,7 @@ def update_game(self, rgame: RareGame):
):
for w in page.findChildren(AchievementWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
page.layout().removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
if ach := rgame.achievements:
@@ -525,11 +527,15 @@ def __init__(self, tag: str, parent=None):
((base_color & 0xFF0000) >> 16) * 0.2126 + ((base_color & 0x00FF00) >> 8) * 0.7152 + (base_color & 0x0000FF) * 0.0722
)
font_color = "white" if luminance < 140 else "black"
- style = ("QCheckBox#{0}{{color: {1};border-color: #{2:x};background-color: #{3:x};}}").format(
+ style = "QCheckBox#{0}{{color: {1};border-color: #{2:x};background-color: #{3:x};}}".format(
self.objectName(), font_color, border_color, base_color
)
self.setStyleSheet(style)
- self.checkStateChanged.connect(lambda state: self.checkStateChangedData.emit(state, self.tag))
+ self.checkStateChanged.connect(self._on_state_changed)
+
+ @Slot(Qt.CheckState)
+ def _on_state_changed(self, state: Qt.CheckState):
+ self.checkStateChangedData.emit(state, self.tag)
def setText(self, text, /):
fm = QFontMetrics(self.font())
diff --git a/rare/components/tabs/library/details/dlcs.py b/rare/components/tabs/library/details/dlcs.py
index 638abc58d8..12a6f1bb40 100644
--- a/rare/components/tabs/library/details/dlcs.py
+++ b/rare/components/tabs/library/details/dlcs.py
@@ -33,7 +33,7 @@ def __init__(self, rgame: RareGame, rdlc: RareGame, parent=None):
# self.image.setPixmap(rdlc.get_pixmap_icon(rdlc.is_installed))
self.__update()
- rdlc.signals.widget.update.connect(self.__update)
+ rdlc.signals.widget.refresh.connect(self.__update)
@Slot()
def __update(self):
@@ -153,6 +153,7 @@ def append_installed(self, rdlc: RareGame):
a_widget: AvailableGameDlcWidget = self.get_available(rdlc.app_name)
if a_widget is not None:
self.ui.available_dlc_container.layout().removeWidget(a_widget)
+ a_widget.disconnect(a_widget)
a_widget.deleteLater()
i_widget: InstalledGameDlcWidget = InstalledGameDlcWidget(self.rgame, rdlc, self.ui.installed_dlc_container)
i_widget.destroyed.connect(self.update_installed_page)
@@ -177,10 +178,12 @@ def update_dlcs(self, rgame: RareGame):
for i_widget in self.list_installed():
self.ui.installed_dlc_container.layout().removeWidget(i_widget)
+ i_widget.disconnect(i_widget)
i_widget.deleteLater()
for a_widget in self.list_available():
self.ui.available_dlc_container.layout().removeWidget(a_widget)
+ a_widget.disconnect(a_widget)
a_widget.deleteLater()
for dlc in sorted(self.rgame.owned_dlcs, key=lambda x: x.app_title):
diff --git a/rare/components/tabs/library/widgets/game_widget.py b/rare/components/tabs/library/widgets/game_widget.py
index 437af65677..36e23e5d2e 100644
--- a/rare/components/tabs/library/widgets/game_widget.py
+++ b/rare/components/tabs/library/widgets/game_widget.py
@@ -45,15 +45,13 @@ def __init__(self, rgame: RareGame, parent=None):
self.install_action.triggered.connect(self._install)
self.desktop_link_action = QAction(self)
- self.desktop_link_action.triggered.connect(lambda: self._create_link(self.rgame.folder_name, "desktop"))
+ self.desktop_link_action.triggered.connect(self._create_link_desktop)
self.menu_link_action = QAction(self)
- self.menu_link_action.triggered.connect(lambda: self._create_link(self.rgame.folder_name, "start_menu"))
+ self.menu_link_action.triggered.connect(self._create_link_start_menu)
self.steam_shortcut_action = QAction(self)
- self.steam_shortcut_action.triggered.connect(
- lambda: self._create_steam_shortcut(self.rgame.app_name, self.rgame.app_title)
- )
+ self.steam_shortcut_action.triggered.connect(self._create_steam_shortcut)
self.reload_action = QAction(self.tr("Reload Image"), self)
self.reload_action.triggered.connect(self._on_reload_image)
@@ -64,15 +62,15 @@ def __init__(self, rgame: RareGame, parent=None):
self.update_actions()
# signals
- self.rgame.signals.widget.update.connect(self.update_pixmap)
- self.rgame.signals.widget.update.connect(self.update_buttons)
- self.rgame.signals.widget.update.connect(self.update_state)
+ self.rgame.signals.widget.refresh.connect(self.update_pixmap)
+ self.rgame.signals.widget.refresh.connect(self.update_buttons)
+ self.rgame.signals.widget.refresh.connect(self.update_state)
self.rgame.signals.game.installed.connect(self.update_actions)
self.rgame.signals.game.uninstalled.connect(self.update_actions)
self.rgame.signals.progress.start.connect(self.start_progress)
- self.rgame.signals.progress.update.connect(lambda p: self.updateProgress(p))
- self.rgame.signals.progress.finish.connect(lambda e: self.hideProgress(e))
+ self.rgame.signals.progress.refresh.connect(self.updateProgress)
+ self.rgame.signals.progress.finish.connect(self.hideProgress)
self.state_strings = {
RareGame.State.IDLE: "",
@@ -134,7 +132,7 @@ def timerEvent(self, a0):
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
- super().showEvent(a0)
+ return super().showEvent(a0)
@Slot()
def update_state(self):
@@ -270,6 +268,14 @@ def _install(self):
def _uninstall(self):
self.show_info.emit(self.rgame)
+ @Slot()
+ def _create_link_desktop(self):
+ self._create_link(self.rgame.folder_name, "desktop")
+
+ @Slot()
+ def _create_link_start_menu(self):
+ self._create_link(self.rgame.folder_name, "start_menu")
+
@Slot(str, str)
def _create_link(self, name: str, link_type: str):
if not desktop_links_supported():
@@ -299,8 +305,9 @@ def _create_link(self, name: str, link_type: str):
shortcut_path.unlink(missing_ok=True)
self.update_actions()
- @Slot(str, str)
- def _create_steam_shortcut(self, app_name: str, app_title: str):
+ @Slot()
+ def _create_steam_shortcut(self):
+ app_name, app_title = self.rgame.app_name, self.rgame.app_title
if steam_shortcut_exists(app_name):
if shortcut := remove_steam_shortcut(app_name):
remove_steam_coverart(shortcut)
diff --git a/rare/components/tabs/library/widgets/library_widget.py b/rare/components/tabs/library/widgets/library_widget.py
index 9d88a57526..f1a43ff34a 100644
--- a/rare/components/tabs/library/widgets/library_widget.py
+++ b/rare/components/tabs/library/widgets/library_widget.py
@@ -1,6 +1,6 @@
from typing import List, Optional, Tuple
-from PySide6.QtCore import QEvent, QObject, Qt
+from PySide6.QtCore import QEvent, QObject, Qt, Slot
from PySide6.QtGui import (
QBrush,
QColor,
@@ -149,12 +149,14 @@ def showProgress(self, color_pm: QPixmap, gray_pm: QPixmap) -> None:
self.progress_label.setVisible(True)
self.updateProgress(0)
+ @Slot(int)
def updateProgress(self, progress: int):
self.progress_label.setText(f"{progress:02}%")
if progress > self._progress:
self._progress = progress
self.setPixmap(self.progressPixmap(self._color_pixmap, self._gray_pixmap, progress))
+ @Slot(bool)
def hideProgress(self, stopped: bool):
self._color_pixmap = None
self._gray_pixmap = None
diff --git a/rare/components/tabs/settings/__init__.py b/rare/components/tabs/settings/__init__.py
index 466d9bc304..28463c705b 100644
--- a/rare/components/tabs/settings/__init__.py
+++ b/rare/components/tabs/settings/__init__.py
@@ -1,5 +1,7 @@
import platform as pf
+from PySide6.QtCore import Signal, Slot
+
from rare.models.settings import RareAppSettings
from rare.shared import RareCore
from rare.widgets.side_tab import SideTabWidget
@@ -13,6 +15,8 @@
class SettingsTab(SideTabWidget):
+ update_available = Signal()
+
def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
super(SettingsTab, self).__init__(parent=parent)
@@ -32,10 +36,15 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.about = About(self)
title = self.tr("About")
self.about_index = self.addTab(self.about, title, title)
- self.about.update_available_ready.connect(lambda: self.tabBar().setTabText(self.about_index, "About (!)"))
+ self.about.update_available.connect(self._on_update_available)
+ self.about.update_available.connect(self.update_available)
if rcore.args().debug:
title = self.tr("Debug")
self.debug_index = self.addTab(DebugSettings(rcore.signals(), self), title, title)
self.setCurrentIndex(self.rare_index)
+
+ @Slot()
+ def _on_update_available(self):
+ self.tabBar().setTabText(self.about_index, "About (!)")
\ No newline at end of file
diff --git a/rare/components/tabs/settings/about.py b/rare/components/tabs/settings/about.py
index ec901e3cda..6dc16fe18a 100644
--- a/rare/components/tabs/settings/about.py
+++ b/rare/components/tabs/settings/about.py
@@ -2,7 +2,7 @@
from logging import getLogger
from typing import Tuple
-from PySide6.QtCore import Signal
+from PySide6.QtCore import Signal, Slot
from PySide6.QtGui import QShowEvent
from PySide6.QtWidgets import QWidget
@@ -23,7 +23,7 @@ def versiontuple(v) -> Tuple[int, ...]:
class About(QWidget):
- update_available_ready = Signal()
+ update_available = Signal()
def __init__(self, parent=None):
super(About, self).__init__(parent=parent)
@@ -33,38 +33,43 @@ def __init__(self, parent=None):
self.ui.version.setText(f"{__version__} {__codename__}")
self.ui.update_label.setEnabled(False)
- self.ui.update_lbl.setEnabled(False)
+ self.ui.update_field.setEnabled(False)
self.ui.open_browser.setVisible(False)
self.ui.open_browser.setEnabled(False)
self.releases_url = "https://api.github.com/repos/RareDevs/Rare/releases/latest"
self.manager = QtRequests(parent=self)
- self.manager.get(self.releases_url, self.update_available_finished)
+ self.manager.get(self.releases_url, self._on_update_check_finished)
- self.ui.open_browser.clicked.connect(lambda: webbrowser.open("https://github.com/RareDevs/Rare/releases/latest"))
+ self.ui.open_browser.clicked.connect(self._on_browser_clicked)
- self.update_available = False
+ self._update_available = False
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
- self.manager.get(self.releases_url, self.update_available_finished)
- super().showEvent(a0)
+ self.manager.get(self.releases_url, self._on_update_check_finished)
+ return super().showEvent(a0)
- def update_available_finished(self, data: dict):
+ @Slot()
+ def _on_browser_clicked(self):
+ webbrowser.open("https://github.com/RareDevs/Rare/releases/latest")
+
+ @Slot(dict)
+ def _on_update_check_finished(self, data: dict):
if latest_tag := data.get("tag_name"):
- self.update_available = versiontuple(latest_tag) > versiontuple(__version__)
+ self._update_available = versiontuple(latest_tag) > versiontuple(__version__)
else:
- self.update_available = False
+ self._update_available = False
- if self.update_available:
+ if self._update_available:
logger.info(f"Update available: {__version__} -> {latest_tag}")
- self.ui.update_lbl.setText(f"{__version__} -> {latest_tag}")
- self.update_available_ready.emit()
+ self.ui.update_field.setText(f"{__version__} -> {latest_tag}")
+ self.update_available.emit()
else:
- self.ui.update_lbl.setText(self.tr("You have the latest version"))
- self.ui.update_label.setEnabled(self.update_available)
- self.ui.update_lbl.setEnabled(self.update_available)
- self.ui.open_browser.setVisible(self.update_available)
- self.ui.open_browser.setEnabled(self.update_available)
+ self.ui.update_field.setText(self.tr("You have the latest version"))
+ self.ui.update_label.setEnabled(self._update_available)
+ self.ui.update_field.setEnabled(self._update_available)
+ self.ui.open_browser.setVisible(self._update_available)
+ self.ui.open_browser.setEnabled(self._update_available)
diff --git a/rare/components/tabs/settings/debug.py b/rare/components/tabs/settings/debug.py
index bb1c3e20e5..08b02856e0 100644
--- a/rare/components/tabs/settings/debug.py
+++ b/rare/components/tabs/settings/debug.py
@@ -1,3 +1,4 @@
+from PySide6.QtCore import Slot
from PySide6.QtWidgets import QPushButton, QVBoxLayout, QWidget
from rare.models.signals import GlobalSignals
@@ -12,7 +13,7 @@ def __init__(self, signals: GlobalSignals, parent=None):
self.raise_runtime_exception_button = QPushButton("Raise Exception", self)
self.raise_runtime_exception_button.clicked.connect(self.raise_exception)
self.restart_button = QPushButton("Restart", self)
- self.restart_button.clicked.connect(lambda: self.signals.application.quit.emit(ExitCodes.LOGOUT))
+ self.restart_button.clicked.connect(self._on_restart_clicked)
self.send_notification_button = QPushButton("Notify", self)
self.send_notification_button.clicked.connect(self.send_notification)
@@ -22,6 +23,10 @@ def __init__(self, signals: GlobalSignals, parent=None):
layout.addWidget(self.send_notification_button)
layout.addStretch(1)
+ @Slot()
+ def _on_restart_clicked(self):
+ self.signals.application.quit.emit(ExitCodes.LOGOUT)
+
def raise_exception(self):
raise RuntimeError("Debug Crash")
diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py
index 69fc9a5250..1a865a306a 100644
--- a/rare/components/tabs/settings/legendary.py
+++ b/rare/components/tabs/settings/legendary.py
@@ -23,14 +23,16 @@
logger = getLogger("LegendarySettings")
+class RefreshGameMetaWorkerSignals(QObject):
+ finished = Signal()
+
+
class RefreshGameMetaWorker(Worker):
- class Signals(QObject):
- finished = Signal()
def __init__(self, core: LegendaryCore, platforms: Set[str], include_unreal: bool):
super(RefreshGameMetaWorker, self).__init__()
self.core = core
- self.signals = RefreshGameMetaWorker.Signals()
+ self.signals = RefreshGameMetaWorkerSignals()
self.platforms = platforms if platforms else {"Windows"}
self.skip_ue = not include_unreal
@@ -57,7 +59,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
placeholder=self.tr("Default installation folder for macOS games"),
file_mode=QFileDialog.FileMode.Directory,
edit_func=self.__path_edit_callback,
- save_func=self.__path_save_callback_mac,
+ save_func=self._path_save_callback_mac,
)
self.ui.install_dir_layout.addWidget(self.mac_install_dir_edit)
@@ -67,7 +69,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
placeholder=self.tr("Default installation folder for Windows games"),
file_mode=QFileDialog.FileMode.Directory,
edit_func=self.__path_edit_callback,
- save_func=self.__path_save_callback_win,
+ save_func=self._path_save_callback_win,
)
self.ui.install_dir_layout.addWidget(self.install_dir_edit)
@@ -88,8 +90,8 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.ui.disable_https_check.checkStateChanged.connect(self.disable_https_save)
# Clean metadata
- self.ui.clean_button.clicked.connect(lambda: self.clean_metadata(keep_manifests=False))
- self.ui.clean_keep_manifests_button.clicked.connect(lambda: self.clean_metadata(keep_manifests=True))
+ self.ui.clean_button.clicked.connect(self._on_clean_clicked)
+ self.ui.clean_keep_manifests_button.clicked.connect(self._on_clean_keep_manifests_clicked)
self.locale_edit = IndicatorLineEdit(
f"{self.core.language_code}-{self.core.country_code}",
@@ -101,32 +103,22 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.ui.locale_layout.addWidget(self.locale_edit)
self.ui.fetch_win32_check.setChecked(self.settings.get_value(app_settings.win32_meta))
- self.ui.fetch_win32_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.win32_meta, s != Qt.CheckState.Unchecked)
- )
+ self.ui.fetch_win32_check.checkStateChanged.connect(self._on_fetch_win32_changed)
self.ui.fetch_macos_check.setChecked(self.settings.get_value(app_settings.macos_meta))
- self.ui.fetch_macos_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.macos_meta, s != Qt.CheckState.Unchecked)
- )
+ self.ui.fetch_macos_check.checkStateChanged.connect(self._on_fetch_macos_changed)
self.ui.fetch_macos_check.setDisabled(pf.system() == "Darwin")
self.ui.fetch_unreal_check.setChecked(self.settings.get_value(app_settings.unreal_meta))
- self.ui.fetch_unreal_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.unreal_meta, s != Qt.CheckState.Unchecked)
- )
+ self.ui.fetch_unreal_check.checkStateChanged.connect(self._on_fetch_unreal_changed)
self.ui.exclude_non_asset_check.setChecked(self.settings.get_value(app_settings.exclude_non_asset))
- self.ui.exclude_non_asset_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.exclude_non_asset, s != Qt.CheckState.Unchecked)
- )
+ self.ui.exclude_non_asset_check.checkStateChanged.connect(self._on_exclude_non_asset_changed)
self.ui.exclude_entitlements_check.setChecked(self.settings.get_value(app_settings.exclude_entitlements))
- self.ui.exclude_entitlements_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.exclude_entitlements, s != Qt.CheckState.Unchecked)
- )
+ self.ui.exclude_entitlements_check.checkStateChanged.connect(self._on_exclude_entitlements_changed)
- self.ui.refresh_metadata_button.clicked.connect(self.refresh_metadata)
+ self.ui.refresh_metadata_button.clicked.connect(self._refresh_metadata)
# FIXME: Disable the button for now because it interferes with RareCore
self.ui.refresh_metadata_button.setEnabled(False)
self.ui.refresh_metadata_button.setVisible(False)
@@ -145,15 +137,19 @@ def hideEvent(self, a0: QHideEvent):
self.core.lgd.save_config()
return super().hideEvent(a0)
- def refresh_metadata(self):
+ @Slot()
+ def _on_refresh_worker_finished(self):
+ self.ui.refresh_metadata_button.setDisabled(False)
+
+ def _refresh_metadata(self):
self.ui.refresh_metadata_button.setDisabled(True)
platforms = set()
if self.ui.fetch_win32_check.isChecked():
platforms.add("Win32")
if self.ui.fetch_macos_check.isChecked():
platforms.add("Mac")
- worker = RefreshGameMetaWorker(platforms, self.ui.fetch_unreal_check.isChecked())
- worker.signals.finished.connect(lambda: self.ui.refresh_metadata_button.setDisabled(False))
+ worker = RefreshGameMetaWorker(self.core, platforms, self.ui.fetch_unreal_check.isChecked())
+ worker.signals.finished.connect(self._on_refresh_worker_finished)
QThreadPool.globalInstance().start(worker)
@staticmethod
@@ -191,16 +187,16 @@ def __path_edit_callback(path: str) -> Tuple[bool, str, int]:
return True, path, IndicatorReasonsCommon.VALID
@Slot(str)
- def __path_save_callback_mac(self, text: str) -> None:
- self.__path_save(text, "mac_install_dir")
+ def _path_save_callback_mac(self, text: str) -> None:
+ self._path_save(text, "mac_install_dir")
@Slot(str)
- def __path_save_callback_win(self, text: str) -> None:
- self.__path_save(text, "install_dir")
+ def _path_save_callback_win(self, text: str) -> None:
+ self._path_save(text, "install_dir")
if pf.system() != "Darwin":
- self.__path_save_callback_mac(text)
+ self._path_save_callback_mac(text)
- def __path_save(self, text: str, option: str):
+ def _path_save(self, text: str, option: str):
if text:
self.core.lgd.config.set("Legendary", option, text)
else:
@@ -256,3 +252,31 @@ def clean_metadata(self, keep_manifests: bool):
)
else:
QMessageBox.information(self, self.tr("Cleanup"), self.tr("Nothing to clean"))
+
+ @Slot()
+ def _on_clean_clicked(self):
+ self.clean_metadata(keep_manifests=False)
+
+ @Slot()
+ def _on_clean_keep_manifests_clicked(self):
+ self.clean_metadata(keep_manifests=True)
+
+ @Slot(Qt.CheckState)
+ def _on_fetch_win32_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.win32_meta, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_fetch_macos_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.macos_meta, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_fetch_unreal_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.unreal_meta, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_exclude_non_asset_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.exclude_non_asset, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_exclude_entitlements_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.exclude_entitlements, state != Qt.CheckState.Unchecked)
diff --git a/rare/components/tabs/settings/rare.py b/rare/components/tabs/settings/rare.py
index a9c278ac14..c5923c998f 100644
--- a/rare/components/tabs/settings/rare.py
+++ b/rare/components/tabs/settings/rare.py
@@ -47,7 +47,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.ui.lang_select.setCurrentIndex(index)
else:
self.ui.lang_select.setCurrentIndex(0)
- self.ui.lang_select.currentIndexChanged.connect(self.on_lang_changed)
+ self.ui.lang_select.currentIndexChanged.connect(self._on_lang_changed)
self.ui.color_select.addItem(self.tr("None"), "")
for item in get_color_schemes():
@@ -59,7 +59,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.ui.style_select.setDisabled(True)
else:
self.ui.color_select.setCurrentIndex(0)
- self.ui.color_select.currentIndexChanged.connect(self.on_color_select_changed)
+ self.ui.color_select.currentIndexChanged.connect(self._on_color_select_changed)
self.ui.style_select.addItem(self.tr("None"), "")
for item in get_style_sheets():
@@ -71,7 +71,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.ui.color_select.setDisabled(True)
else:
self.ui.style_select.setCurrentIndex(0)
- self.ui.style_select.currentIndexChanged.connect(self.on_style_select_changed)
+ self.ui.style_select.currentIndexChanged.connect(self._on_style_select_changed)
self.ui.view_combo.addItem(self.tr("Game covers"), LibraryView.COVER)
self.ui.view_combo.addItem(self.tr("Vertical list"), LibraryView.VLIST)
@@ -80,56 +80,36 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
self.ui.view_combo.setCurrentIndex(index)
else:
self.ui.view_combo.setCurrentIndex(0)
- self.ui.view_combo.currentIndexChanged.connect(self.on_view_combo_changed)
+ self.ui.view_combo.currentIndexChanged.connect(self._on_view_combo_changed)
self.discord_rpc_settings = DiscordRPCSettings(settings, rcore.signals(), self)
self.ui.right_layout.insertWidget(1, self.discord_rpc_settings, alignment=Qt.AlignmentFlag.AlignTop)
self.ui.sys_tray_close.setChecked(self.settings.get_value(app_settings.sys_tray_close))
- self.ui.sys_tray_close.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.sys_tray_close, s != Qt.CheckState.Unchecked)
- )
+ self.ui.sys_tray_close.checkStateChanged.connect(self._on_sys_tray_close_changed)
self.ui.sys_tray_start.setChecked(self.settings.get_value(app_settings.sys_tray_start))
- self.ui.sys_tray_start.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.sys_tray_start, s != Qt.CheckState.Unchecked)
- )
-
- # Disable starting in system tray if closing to system tray is disabled.
- self.ui.sys_tray_close.checkStateChanged.connect(lambda: self.ui.sys_tray_start.setChecked(False))
- self.ui.sys_tray_close.checkStateChanged.connect(
- lambda s: self.ui.sys_tray_start.setEnabled(s != Qt.CheckState.Unchecked)
- )
+ self.ui.sys_tray_start.checkStateChanged.connect(self._on_sys_tray_start_changed)
self.ui.auto_update.setChecked(self.settings.get_value(app_settings.auto_update))
- self.ui.auto_update.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.auto_update, s != Qt.CheckState.Unchecked)
- )
+ self.ui.auto_update.checkStateChanged.connect(self._on_auto_update_changed)
self.ui.confirm_start.setChecked(self.settings.get_value(app_settings.confirm_start))
- self.ui.confirm_start.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.confirm_start, s != Qt.CheckState.Unchecked)
- )
+ self.ui.confirm_start.checkStateChanged.connect(self._on_confirm_start_changed)
# TODO: implement use when starting game, disable for now
self.ui.confirm_start.setDisabled(True)
self.ui.auto_sync_cloud.setChecked(self.settings.get_value(app_settings.auto_sync_cloud))
- self.ui.auto_sync_cloud.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.auto_sync_cloud, s != Qt.CheckState.Unchecked)
- )
+ self.ui.auto_sync_cloud.checkStateChanged.connect(self._on_auto_sync_cloud_changed)
self.ui.notification.setChecked(self.settings.get_value(app_settings.notification))
- self.ui.notification.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.notification, s != Qt.CheckState.Unchecked)
- )
+ self.ui.notification.checkStateChanged.connect(self._on_notification_changed)
self.ui.save_size.setChecked(self.settings.get_value(app_settings.restore_window))
- self.ui.save_size.checkStateChanged.connect(self.save_window_size)
+ self.ui.save_size.checkStateChanged.connect(self._on_save_size_changed)
self.ui.log_games.setChecked(self.settings.get_value(app_settings.log_games))
- self.ui.log_games.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.log_games, s != Qt.CheckState.Unchecked)
- )
+ self.ui.log_games.checkStateChanged.connect(self._on_log_games_changed)
if desktop_links_supported():
self.desktop_link = desktop_link_path("Rare", "desktop")
@@ -148,20 +128,48 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
if self.start_menu_link and self.start_menu_link.exists():
self.ui.startmenu_link_btn.setText(self.tr("Remove start menu link"))
- self.ui.desktop_link_btn.clicked.connect(self.create_desktop_link)
- self.ui.startmenu_link_btn.clicked.connect(self.create_start_menu_link)
+ self.ui.desktop_link_btn.clicked.connect(self._create_desktop_link)
+ self.ui.startmenu_link_btn.clicked.connect(self._create_start_menu_link)
- self.ui.log_dir_open_button.clicked.connect(self.open_directory)
- self.ui.log_dir_clean_button.clicked.connect(self.clean_logdir)
+ self.ui.log_dir_open_button.clicked.connect(self._open_directory)
+ self.ui.log_dir_clean_button.clicked.connect(self._clean_logdir)
# get size of logdir
size = sum(log_dir().joinpath(f).stat().st_size for f in log_dir().iterdir() if log_dir().joinpath(f).is_file())
self.ui.log_dir_size_label.setText(format_size(size))
- # self.log_dir_clean_button.setVisible(False)
- # self.log_dir_size_label.setVisible(False)
+
+ @Slot(Qt.CheckState)
+ def _on_sys_tray_close_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.sys_tray_close, state != Qt.CheckState.Unchecked)
+ self.ui.sys_tray_start.setChecked(False)
+ self.ui.sys_tray_start.setEnabled(state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_sys_tray_start_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.sys_tray_start, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_auto_update_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.auto_update, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_confirm_start_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.confirm_start, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_auto_sync_cloud_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.auto_sync_cloud, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_notification_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.notification, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_log_games_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.log_games, state != Qt.CheckState.Unchecked)
@Slot()
- def clean_logdir(self):
+ def _clean_logdir(self):
for f in log_dir().iterdir():
try:
if log_dir().joinpath(f).is_file():
@@ -172,7 +180,7 @@ def clean_logdir(self):
self.ui.log_dir_size_label.setText(format_size(size))
@Slot()
- def create_start_menu_link(self):
+ def _create_start_menu_link(self):
try:
if not os.path.exists(self.start_menu_link):
if not create_desktop_link(app_name="rare_shortcut", link_type="start_menu"):
@@ -190,7 +198,7 @@ def create_start_menu_link(self):
)
@Slot()
- def create_desktop_link(self):
+ def _create_desktop_link(self):
try:
if not os.path.exists(self.desktop_link):
if not create_desktop_link(app_name="rare_shortcut", link_type="desktop"):
@@ -208,7 +216,7 @@ def create_desktop_link(self):
)
@Slot(int)
- def on_color_select_changed(self, index: int):
+ def _on_color_select_changed(self, index: int):
scheme = self.ui.color_select.itemData(index, Qt.ItemDataRole.UserRole)
if scheme:
self.ui.style_select.setCurrentIndex(0)
@@ -219,7 +227,7 @@ def on_color_select_changed(self, index: int):
set_color_pallete(scheme)
@Slot(int)
- def on_style_select_changed(self, index: int):
+ def _on_style_select_changed(self, index: int):
style = self.ui.style_select.itemData(index, Qt.ItemDataRole.UserRole)
if style:
self.ui.color_select.setCurrentIndex(0)
@@ -230,22 +238,22 @@ def on_style_select_changed(self, index: int):
set_style_sheet(style)
@Slot(int)
- def on_view_combo_changed(self, index: int):
+ def _on_view_combo_changed(self, index: int):
view = LibraryView(self.ui.view_combo.itemData(index, Qt.ItemDataRole.UserRole))
self.settings.set_value(app_settings.library_view, view)
@Slot()
- def open_directory(self):
+ def _open_directory(self):
QDesktopServices.openUrl(QUrl.fromLocalFile(log_dir()))
@Slot(Qt.CheckState)
- def save_window_size(self, state: Qt.CheckState):
+ def _on_save_size_changed(self, state: Qt.CheckState):
self.settings.set_value(app_settings.restore_window, state != Qt.CheckState.Unchecked)
self.settings.rem_value(app_settings.window_width)
self.settings.rem_value(app_settings.window_height)
@Slot(int)
- def on_lang_changed(self, index: int):
+ def _on_lang_changed(self, index: int):
lang_code = self.ui.lang_select.itemData(index, Qt.ItemDataRole.UserRole)
if lang_code == locale.getlocale()[0]:
self.settings.rem_value(app_settings.language)
diff --git a/rare/components/tabs/settings/widgets/discord_rpc.py b/rare/components/tabs/settings/widgets/discord_rpc.py
index e9406a2e44..088a19c183 100644
--- a/rare/components/tabs/settings/widgets/discord_rpc.py
+++ b/rare/components/tabs/settings/widgets/discord_rpc.py
@@ -1,6 +1,6 @@
import importlib.util
-from PySide6.QtCore import Qt
+from PySide6.QtCore import Qt, Slot
from PySide6.QtWidgets import QGroupBox
from rare.models.settings import DiscordRPCMode, RareAppSettings, app_settings
@@ -28,28 +28,35 @@ def __init__(self, settings: RareAppSettings, signals: GlobalSignals, parent):
self.ui.mode_combo.setCurrentIndex(self.ui.mode_combo.findData(rpc_mode, Qt.ItemDataRole.UserRole))
else:
self.ui.mode_combo.setCurrentIndex(index)
- self.ui.mode_combo.currentIndexChanged.connect(self.__mode_changed)
+ self.ui.mode_combo.currentIndexChanged.connect(self._mode_changed)
self.ui.game_check.setChecked(self.settings.get_value(app_settings.discord_rpc_game))
- self.ui.game_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.discord_rpc_game, s != Qt.CheckState.Unchecked)
- )
+ self.ui.game_check.checkStateChanged.connect(self._on_game_changed)
self.ui.os_check.setChecked(self.settings.get_value(app_settings.discord_rpc_os))
- self.ui.os_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.discord_rpc_os, s != Qt.CheckState.Unchecked)
- )
+ self.ui.os_check.checkStateChanged.connect(self._on_os_changed)
self.ui.time_check.setChecked(self.settings.get_value(app_settings.discord_rpc_time))
- self.ui.time_check.checkStateChanged.connect(
- lambda s: self.settings.set_value(app_settings.discord_rpc_time, s != Qt.CheckState.Unchecked)
- )
+ self.ui.time_check.checkStateChanged.connect(self._on_time_changed)
if not importlib.util.find_spec("pypresence"):
self.setDisabled(True)
self.setToolTip(self.tr("Pypresence is not installed"))
- def __mode_changed(self, index):
+ @Slot(Qt.CheckState)
+ def _on_game_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.discord_rpc_game, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_os_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.discord_rpc_os, state != Qt.CheckState.Unchecked)
+
+ @Slot(Qt.CheckState)
+ def _on_time_changed(self, state: Qt.CheckState):
+ self.settings.set_value(app_settings.discord_rpc_time, state != Qt.CheckState.Unchecked)
+
+ @Slot(int)
+ def _mode_changed(self, index: int):
data = self.ui.mode_combo.itemData(index, Qt.ItemDataRole.UserRole)
self.settings.set_value(app_settings.discord_rpc_mode, data)
self.signals.discord_rpc.update_settings.emit()
diff --git a/rare/components/tabs/settings/widgets/wrappers.py b/rare/components/tabs/settings/widgets/wrappers.py
index d2f9e2086c..cfa7b58838 100644
--- a/rare/components/tabs/settings/widgets/wrappers.py
+++ b/rare/components/tabs/settings/widgets/wrappers.py
@@ -164,11 +164,13 @@ def data(self) -> Wrapper:
def __on_state_changed(self, state: Qt.CheckState) -> None:
new_wrapper = Wrapper(command=self.wrapper.command, enabled=self.text_lbl.isChecked())
self.update_wrapper.emit(self.wrapper, new_wrapper)
+ self.disconnect(self)
self.deleteLater()
@Slot()
def __on_delete(self) -> None:
self.delete_wrapper.emit(self.wrapper)
+ self.disconnect(self)
self.deleteLater()
@Slot()
@@ -183,6 +185,7 @@ def __on_edit_result(self, accepted: bool, command: str):
if accepted and command:
new_wrapper = Wrapper(command=shlex.split(command))
self.update_wrapper.emit(self.wrapper, new_wrapper)
+ self.disconnect(self)
self.deleteLater()
def mouseMoveEvent(self, a0: QMouseEvent) -> None:
@@ -381,6 +384,7 @@ def __update_wrapper(self, old: Wrapper, new: Wrapper):
@Slot()
def update_state(self):
for w in self.wrapper_container.findChildren(WrapperWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
+ w.disconnect(w)
w.deleteLater()
wrappers = self.wrappers.get_wrappers(self.app_name)
if not wrappers:
diff --git a/rare/components/tabs/store/landing.py b/rare/components/tabs/store/landing.py
index 7072b496b8..820c54d42f 100644
--- a/rare/components/tabs/store/landing.py
+++ b/rare/components/tabs/store/landing.py
@@ -102,8 +102,8 @@ def __init__(self, api: StoreAPI, parent=None):
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
- self.api.get_free(self.__update_free_games)
- self.api.get_wishlist(self.__update_wishlist_discounts)
+ self.api.get_free(self._update_free_games)
+ self.api.get_wishlist(self._update_wishlist_discounts)
return super().showEvent(a0)
def hideEvent(self, a0: QHideEvent) -> None:
@@ -112,9 +112,10 @@ def hideEvent(self, a0: QHideEvent) -> None:
# TODO: Implement tab unloading
return super().hideEvent(a0)
- def __update_wishlist_discounts(self, wishlist: List[WishlistItemModel]):
+ def _update_wishlist_discounts(self, wishlist: List[WishlistItemModel]):
for w in self.discounts_group.findChildren(StoreItemWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
self.discounts_group.layout().removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
for item in filter(lambda x: bool(x.offer.price.totalPrice.discount), wishlist):
@@ -125,13 +126,15 @@ def __update_wishlist_discounts(self, wishlist: List[WishlistItemModel]):
self.discounts_group.setVisible(have_discounts)
self.discounts_group.loading(False)
- def __update_free_games(self, free_games: List[CatalogOfferModel]):
+ def _update_free_games(self, free_games: List[CatalogOfferModel]):
for w in self.free_games_now.findChildren(StoreItemWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
self.free_games_now.layout().removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
for w in self.free_games_next.findChildren(StoreItemWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
self.free_games_next.layout().removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
date = datetime.now(timezone.utc)
@@ -182,6 +185,7 @@ def show_games(self, data):
for w in self.games_group.findChildren(StoreItemWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
self.games_group.layout().removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
for game in data:
diff --git a/rare/components/tabs/store/search.py b/rare/components/tabs/store/search.py
index cf6522ac98..4284b02fd0 100644
--- a/rare/components/tabs/store/search.py
+++ b/rare/components/tabs/store/search.py
@@ -101,16 +101,37 @@ def show_search_results(self):
# self.show_info.emit(self.search_bar.text())
def init_filter(self):
- self.ui.none_price.toggled.connect(lambda: self.prepare_request("") if self.ui.none_price.isChecked() else None)
- self.ui.free_button.toggled.connect(lambda: self.prepare_request("free") if self.ui.free_button.isChecked() else None)
- self.ui.under10.toggled.connect(lambda: self.prepare_request("[0, 1000)") if self.ui.under10.isChecked() else None)
- self.ui.under20.toggled.connect(lambda: self.prepare_request("[0, 2000)") if self.ui.under20.isChecked() else None)
- self.ui.under30.toggled.connect(lambda: self.prepare_request("[0, 3000)") if self.ui.under30.isChecked() else None)
- self.ui.above.toggled.connect(lambda: self.prepare_request("[1499,]") if self.ui.above.isChecked() else None)
+ # self.ui.none_price.toggled.connect(lambda: self.prepare_request("") if self.ui.none_price.isChecked() else None)
+ self.ui.none_price.toggled.connect(
+ (lambda obj: obj.prepare_request("") if self.ui.none_price.isChecked() else None).__get__(self)
+ )
+ # self.ui.free_button.toggled.connect(lambda: self.prepare_request("free") if self.ui.free_button.isChecked() else None)
+ self.ui.free_button.toggled.connect(
+ (lambda obj: obj.prepare_request("free") if self.ui.free_button.isChecked() else None).__get__(self)
+ )
+ # self.ui.under10.toggled.connect(lambda: self.prepare_request("[0, 1000)") if self.ui.under10.isChecked() else None)
+ self.ui.under10.toggled.connect(
+ (lambda obj: obj.prepare_request("[0, 1000)") if self.ui.under10.isChecked() else None).__get__(self)
+ )
+ # self.ui.under20.toggled.connect(lambda: self.prepare_request("[0, 2000)") if self.ui.under20.isChecked() else None)
+ self.ui.under20.toggled.connect(
+ (lambda obj: obj.prepare_request("[0, 2000)") if self.ui.under20.isChecked() else None).__get__(self)
+ )
+ # self.ui.under30.toggled.connect(lambda: self.prepare_request("[0, 3000)") if self.ui.under30.isChecked() else None)
+ self.ui.under30.toggled.connect(
+ (lambda obj: obj.prepare_request("[0, 3000)") if self.ui.under30.isChecked() else None).__get__(self)
+ )
+ # self.ui.above.toggled.connect(lambda: self.prepare_request("[1499,]") if self.ui.above.isChecked() else None)
+ self.ui.above.toggled.connect(
+ (lambda obj: obj.prepare_request("[1499,]") if self.ui.above.isChecked() else None).__get__(self)
+ )
# self.on_discount.toggled.connect(
# lambda: self.prepare_request("sale") if self.on_discount.isChecked() else None
# )
- self.ui.on_discount.toggled.connect(lambda: self.prepare_request())
+ # self.ui.on_discount.toggled.connect(lambda: self.prepare_request())
+ self.ui.on_discount.toggled.connect(
+ (lambda obj: obj.prepare_request()).__get__(self)
+ )
constants = Constants()
self.checkboxes = []
@@ -123,8 +144,14 @@ def init_filter(self):
]:
for text, tag in variables:
checkbox = CheckBox(text, tag)
- checkbox.activated.connect(lambda x: self.prepare_request(added_tag=x))
- checkbox.deactivated.connect(lambda x: self.prepare_request(removed_tag=x))
+ # checkbox.activated.connect(lambda x: self.prepare_request(added_tag=x))
+ checkbox.activated.connect(
+ (lambda obj, x: obj.prepare_request(added_tag=x)).__get__(self)
+ )
+ # checkbox.deactivated.connect(lambda x: self.prepare_request(removed_tag=x))
+ checkbox.deactivated.connect(
+ (lambda obj, x: obj.prepare_request(removed_tag=x)).__get__(self)
+ )
groupbox.layout().addWidget(checkbox)
self.checkboxes.append(checkbox)
self.ui.reset_button.clicked.connect(self.reset_filters)
@@ -237,9 +264,11 @@ def load_results(self, text: str):
def show_results(self, results: dict):
for w in self.results_container.findChildren(QLabel, options=Qt.FindChildOption.FindDirectChildrenOnly):
self.results_layout.removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
for w in self.results_container.findChildren(SearchItemWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
self.results_layout.removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
if not results:
@@ -247,7 +276,7 @@ def show_results(self, results: dict):
else:
for res in results:
w = SearchItemWidget(self.store_api.cached_manager, res, parent=self.results_container)
- w.show_details.connect(self.show_details.emit)
+ w.show_details.connect(self.show_details)
self.results_layout.addWidget(w)
self.results_layout.update()
self.setEnabled(True)
diff --git a/rare/components/tabs/store/store_api.py b/rare/components/tabs/store/store_api.py
index ff8f7da3f7..1d513b74aa 100644
--- a/rare/components/tabs/store/store_api.py
+++ b/rare/components/tabs/store/store_api.py
@@ -1,4 +1,5 @@
from logging import getLogger
+from typing import Callable, Tuple
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication
@@ -45,7 +46,7 @@ def __init__(self, token, language: str, country: str, installed):
self.browse_active = False
self.next_browse_request = tuple(())
- def get_free(self, callback: callable):
+ def get_free(self, callback: Callable):
url = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions"
params = {
"locale": self.locale,
@@ -54,7 +55,7 @@ def get_free(self, callback: callable):
}
self.manager.get(url, lambda data: self.__handle_free_games(data, callback), params=params)
- def __handle_free_games(self, data, callback):
+ def __handle_free_games(self, data, callback: Callable):
try:
response = ResponseModel.from_dict(data)
if response.errors:
@@ -68,7 +69,7 @@ def __handle_free_games(self, data, callback):
self.logger.error("Free games request failed with: %s", e)
callback(elements)
- def get_wishlist(self, callback):
+ def get_wishlist(self, callback: Callable):
self.authed_manager.post(
graphql_url,
lambda data: self.__handle_wishlist(data, callback),
@@ -82,7 +83,7 @@ def get_wishlist(self, callback):
},
)
- def __handle_wishlist(self, data, callback):
+ def __handle_wishlist(self, data, callback: Callable[[Tuple], None]):
try:
response = ResponseModel.from_dict(data)
if response.errors:
@@ -96,7 +97,7 @@ def __handle_wishlist(self, data, callback):
self.logger.error("Wishlist request failed with: %s", e)
callback(elements)
- def search_game(self, name, callback):
+ def search_game(self, name, callback: Callable):
payload = {
"query": search_query,
"variables": {
@@ -116,7 +117,7 @@ def search_game(self, name, callback):
self.manager.post(graphql_url, lambda data: self.__handle_search(data, callback), payload)
- def __handle_search(self, data, callback):
+ def __handle_search(self, data, callback: Callable[[Tuple], None]):
try:
response = ResponseModel.from_dict(data)
if response.errors:
@@ -177,7 +178,7 @@ def __make_graphql_query(self):
def __make_api_query(self):
pass
- def get_game_config_cms(self, slug: str, is_bundle: bool, callback):
+ def get_game_config_cms(self, slug: str, is_bundle: bool, callback: Callable):
url = "https://store-content.ak.epicgames.com/api"
url += f"/{self.locale}/content/{'products' if not is_bundle else 'bundles'}/{slug}"
self.logger.debug("Quering game config: %s", url)
@@ -194,7 +195,7 @@ def __handle_get_game(self, data, callback):
# callback({})
# needs a captcha
- def add_to_wishlist(self, namespace, offer_id, callback: callable):
+ def add_to_wishlist(self, namespace, offer_id, callback: Callable):
payload = {
"query": wishlist_add_query,
"variables": {
@@ -225,7 +226,7 @@ def _handle_add_to_wishlist(self, data, callback):
callback(success)
self.update_wishlist.emit()
- def remove_from_wishlist(self, namespace, offer_id, callback: callable):
+ def remove_from_wishlist(self, namespace, offer_id, callback: Callable):
payload = {
"query": wishlist_remove_query,
"variables": {
diff --git a/rare/components/tabs/store/widgets/details.py b/rare/components/tabs/store/widgets/details.py
index 1ad4100696..6221055775 100644
--- a/rare/components/tabs/store/widgets/details.py
+++ b/rare/components/tabs/store/widgets/details.py
@@ -1,7 +1,7 @@
from logging import getLogger
from typing import List
-from PySide6.QtCore import Qt, QUrl, Signal
+from PySide6.QtCore import Qt, QUrl, Signal, Slot
from PySide6.QtGui import QDesktopServices, QKeyEvent
from PySide6.QtWidgets import (
QGridLayout,
@@ -83,6 +83,7 @@ def update_game(self, offer: CatalogOfferModel):
# lk: delete tabs in reverse order because indices are updated on deletion
while self.requirements_tabs.count():
+ self.requirements_tabs.widget(0).disconnect(self.requirements_tabs.widget(0))
self.requirements_tabs.widget(0).deleteLater()
self.requirements_tabs.removeTab(0)
self.requirements_tabs.clear()
@@ -202,6 +203,7 @@ def data_received(self, product: DieselProduct):
# clear Layout
for b in self.ui.social_links.findChildren(SocialButton, options=Qt.FindChildOption.FindDirectChildrenOnly):
self.ui.social_links_layout.removeWidget(b)
+ b.disconnect(b)
b.deleteLater()
links = product_data.socialLinks
@@ -248,9 +250,13 @@ def __init__(self, icn, url, parent=None):
self.setFixedSize(36, 36)
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.url = url
- self.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url)))
+ self.clicked.connect(self._on_clicked)
self.setToolTip(url)
+ @Slot()
+ def _on_clicked(self):
+ QDesktopServices.openUrl(QUrl(self.url))
+
class RequirementsWidget(QWidget, SideTabContents):
def __init__(self, system: DieselSystemDetail, parent=None):
diff --git a/rare/components/tabs/store/widgets/items.py b/rare/components/tabs/store/widgets/items.py
index 3e2528df9c..f496f8d755 100644
--- a/rare/components/tabs/store/widgets/items.py
+++ b/rare/components/tabs/store/widgets/items.py
@@ -1,6 +1,6 @@
from logging import getLogger
-from PySide6.QtCore import Qt, Signal
+from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtGui import QMouseEvent
from PySide6.QtWidgets import QPushButton
@@ -106,6 +106,7 @@ def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=
super(WishlistItemWidget, self).__init__(manager, catalog_game, parent=parent)
self.setFixedSize(ImageSize.DisplayWide)
self.ui.setupUi(self)
+
for attr in catalog_game.customAttributes:
if attr["key"] == "developerName":
developer = attr["value"]
@@ -130,5 +131,9 @@ def __init__(self, manager: QtRequests, catalog_game: CatalogOfferModel, parent=
self.delete_button = QPushButton(self)
self.delete_button.setIcon(qta_icon("mdi.delete", color="white"))
- self.delete_button.clicked.connect(lambda: self.delete_from_wishlist.emit(self.catalog_game))
+ self.delete_button.clicked.connect(self._on_delete_clicked)
self.layout().insertWidget(0, self.delete_button, alignment=Qt.AlignmentFlag.AlignRight)
+
+ @Slot()
+ def _on_delete_clicked(self):
+ self.delete_from_wishlist.emit(self.catalog_game)
diff --git a/rare/components/tabs/store/wishlist.py b/rare/components/tabs/store/wishlist.py
index e250a76072..43d3a64205 100644
--- a/rare/components/tabs/store/wishlist.py
+++ b/rare/components/tabs/store/wishlist.py
@@ -92,17 +92,17 @@ def __init__(self, api: StoreAPI, parent=None):
self.ui.order_combo.currentIndexChanged.connect(self.order_wishlist)
self.ui.reload_button.setIcon(qta_icon("fa.refresh", "fa5s.sync", color="white"))
- self.ui.reload_button.clicked.connect(self.__update_widget)
+ self.ui.reload_button.clicked.connect(self._update_widget)
- self.ui.reverse_check.stateChanged.connect(lambda: self.order_wishlist(self.ui.order_combo.currentIndex()))
+ self.ui.reverse_check.stateChanged.connect(self._on_reverse_changed)
self.setEnabled(False)
def showEvent(self, a0: QShowEvent) -> None:
- self.__update_widget()
+ self._update_widget()
return super().showEvent(a0)
- def __update_widget(self):
+ def _update_widget(self):
self.setEnabled(False)
self.api.get_wishlist(self.set_wishlist)
@@ -110,7 +110,7 @@ def delete_from_wishlist(self, game: CatalogOfferModel):
self.api.remove_from_wishlist(
game.namespace,
game.id,
- lambda success: self.__update_widget()
+ lambda success: self._update_widget()
if success
else QMessageBox.warning(self, "Error", self.tr("Could not remove game from wishlist")),
)
@@ -130,6 +130,13 @@ def filter_wishlist(self, index: int = int(WishlistFilter.NONE)):
have_visible = any(map(lambda x: x.isVisible(), widgets))
self.ui.no_games_label.setVisible(not have_visible)
+ __ordering = {
+ WishlistOrder.NAME: lambda x: x.catalog_game.title,
+ WishlistOrder.PRICE: lambda x: x.catalog_game.price.totalPrice.discountPrice,
+ WishlistOrder.DEVELOPER: lambda x: x.catalog_game.seller["name"],
+ WishlistOrder.DISCOUNT: lambda x: 1 - (x.catalog_game.price.totalPrice.discountPrice / x.catalog_game.price.totalPrice.originalPrice)
+ }
+
@Slot(int)
def order_wishlist(self, index: int = int(WishlistOrder.NAME)):
list_order = self.ui.order_combo.itemData(index, Qt.ItemDataRole.UserRole)
@@ -137,34 +144,16 @@ def order_wishlist(self, index: int = int(WishlistOrder.NAME)):
for w in widgets:
self.wishlist_layout.removeWidget(w)
- if list_order == WishlistOrder.NAME:
-
- def func(x: WishlistItemWidget):
- return x.catalog_game.title
- elif list_order == WishlistOrder.PRICE:
-
- def func(x: WishlistItemWidget):
- return x.catalog_game.price.totalPrice.discountPrice
- elif list_order == WishlistOrder.DEVELOPER:
-
- def func(x: WishlistItemWidget):
- return x.catalog_game.seller["name"]
- elif list_order == WishlistOrder.DISCOUNT:
-
- def func(x: WishlistItemWidget):
- discount = x.catalog_game.price.totalPrice.discountPrice
- original = x.catalog_game.price.totalPrice.originalPrice
- return 1 - (discount / original)
- else:
-
- def func(x: WishlistItemWidget):
- return x.catalog_game.title
-
reverse = self.ui.reverse_check.isChecked()
- widgets = sorted(widgets, key=func, reverse=reverse)
+ widgets = sorted(widgets, key=self.__ordering[list_order], reverse=reverse)
for w in widgets:
self.wishlist_layout.addWidget(w)
+ @Slot(Qt.CheckState)
+ def _on_reverse_changed(self, state: Qt.CheckState):
+ self.order_wishlist(self.ui.order_combo.currentIndex())
+
+ @Slot(object)
def set_wishlist(self, wishlist: List[WishlistItemModel] = None):
if wishlist and wishlist[0] == "error":
return
@@ -172,17 +161,24 @@ def set_wishlist(self, wishlist: List[WishlistItemModel] = None):
widgets = self.ui.container.findChildren(WishlistItemWidget, options=Qt.FindChildOption.FindDirectChildrenOnly)
for w in widgets:
self.wishlist_layout.removeWidget(w)
+ w.disconnect(w)
w.deleteLater()
self.ui.no_games_label.setVisible(bool(wishlist))
+ widgets = []
for game in wishlist:
w = WishlistItemWidget(self.api.cached_manager, game.offer, self.ui.container)
w.show_details.connect(self.show_details)
w.delete_from_wishlist.connect(self.delete_from_wishlist)
+ widgets.append(w)
+
+ list_order = self.ui.order_combo.currentData(Qt.ItemDataRole.UserRole)
+ reverse = self.ui.reverse_check.isChecked()
+ widgets = sorted(widgets, key=self.__ordering[list_order], reverse=reverse)
+ for w in widgets:
self.wishlist_layout.addWidget(w)
- self.order_wishlist(self.ui.order_combo.currentIndex())
self.filter_wishlist(self.ui.filter_combo.currentIndex())
self.setEnabled(True)
diff --git a/rare/components/tray_icon.py b/rare/components/tray_icon.py
index 15f42457ea..a17dfe851a 100644
--- a/rare/components/tray_icon.py
+++ b/rare/components/tray_icon.py
@@ -43,7 +43,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None):
# We need to reference this separator to add game actions before it
self.separator = self.menu.addSeparator()
self.exit_action = QAction(self.tr("Quit"))
- self.exit_action.triggered.connect(lambda: self.exit_app.emit(0))
+ self.exit_action.triggered.connect(self._on_exit_triggered)
self.menu.addAction(self.exit_action)
self.game_actions: List[QAction] = []
@@ -60,6 +60,10 @@ def last_played(self) -> List:
last_played.sort(key=lambda g: g.metadata.last_played, reverse=True)
return last_played[:5]
+ @Slot()
+ def _on_exit_triggered(self):
+ self.exit_app.emit(0)
+
@Slot(str, str)
def notify(self, title: str, body: str):
if self.settings.get_value(app_settings.notification):
diff --git a/rare/models/game.py b/rare/models/game.py
index 55dd842eb7..06012d7266 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -14,7 +14,7 @@
from PySide6.QtGui import QPixmap
from rare.lgndr.core import LegendaryCore
-from rare.models.base_game import RareGameBase, RareGameSlim
+from rare.models.game_slim import RareGameBase, RareGameSlim
from rare.models.image import ImageSize
from rare.models.install import InstallOptionsModel, UninstallOptionsModel
from rare.models.settings import RareAppSettings, app_settings
@@ -26,58 +26,61 @@
from rare.utils.workarounds import apply_workarounds
-class RareGame(RareGameSlim):
- @dataclass
- class Metadata:
- queued: bool = False
- queue_pos: Optional[int] = None
- last_played: datetime = datetime.min.replace(tzinfo=timezone.utc)
- achievements_date: datetime = datetime.min.replace(tzinfo=timezone.utc)
- grant_date: datetime = datetime.min.replace(tzinfo=timezone.utc)
- steam_appid: Optional[str] = None
- steam_grade: Optional[str] = None
- steam_date: datetime = datetime.min.replace(tzinfo=timezone.utc)
- steam_shortcut: Optional[int] = None
- tags: Tuple[str, ...] = field(default_factory=tuple)
-
- # For compatibility with previously created game metadata
- @staticmethod
- def parse_date(strdate: str):
- dt = datetime.fromisoformat(strdate) if strdate else datetime.min
- return dt.replace(tzinfo=timezone.utc)
-
- @classmethod
- def from_dict(cls, data: Dict):
- return cls(
- queued=data.get("queued", False),
- queue_pos=data.get("queue_pos", None),
- last_played=RareGame.Metadata.parse_date(data.get("last_played", "")),
- achievements_date=RareGame.Metadata.parse_date(data.get("achievements_date", "")),
- grant_date=RareGame.Metadata.parse_date(data.get("grant_date", "")),
- steam_appid=str(appid) if (appid := data.get("steam_appid", "")) else None,
- steam_grade=data.get("steam_grade", None),
- steam_date=RareGame.Metadata.parse_date(data.get("steam_date", "")),
- steam_shortcut=data.get("steam_shortcut", None),
- tags=data.get("tags", ()),
- )
+@dataclass
+class RareGameMetadata:
+ queued: bool = False
+ queue_pos: Optional[int] = None
+ last_played: datetime = datetime.min.replace(tzinfo=timezone.utc)
+ achievements_date: datetime = datetime.min.replace(tzinfo=timezone.utc)
+ grant_date: datetime = datetime.min.replace(tzinfo=timezone.utc)
+ steam_appid: Optional[str] = None
+ steam_grade: Optional[str] = None
+ steam_date: datetime = datetime.min.replace(tzinfo=timezone.utc)
+ steam_shortcut: Optional[int] = None
+ tags: Tuple[str, ...] = field(default_factory=tuple)
+
+ # For compatibility with previously created game metadata
+ @staticmethod
+ def parse_date(strdate: str):
+ dt = datetime.fromisoformat(strdate) if strdate else datetime.min
+ return dt.replace(tzinfo=timezone.utc)
+
+ @classmethod
+ def from_dict(cls, data: Dict):
+ return cls(
+ queued=data.get("queued", False),
+ queue_pos=data.get("queue_pos", None),
+ last_played=RareGameMetadata.parse_date(data.get("last_played", "")),
+ achievements_date=RareGameMetadata.parse_date(data.get("achievements_date", "")),
+ grant_date=RareGameMetadata.parse_date(data.get("grant_date", "")),
+ steam_appid=str(appid) if (appid := data.get("steam_appid", "")) else None,
+ steam_grade=data.get("steam_grade", None),
+ steam_date=RareGameMetadata.parse_date(data.get("steam_date", "")),
+ steam_shortcut=data.get("steam_shortcut", None),
+ tags=data.get("tags", ()),
+ )
- @property
- def __dict__(self):
- return dict(
- queued=self.queued,
- queue_pos=self.queue_pos,
- last_played=self.last_played.isoformat() if self.last_played else datetime.min.replace(tzinfo=timezone.utc),
- achievements_date=self.last_played.isoformat() if self.achievements_date else datetime.min.replace(tzinfo=timezone.utc),
- grant_date=self.grant_date.isoformat() if self.grant_date else datetime.min.replace(tzinfo=timezone.utc),
- steam_appid=str(self.steam_appid) if self.steam_appid else None,
- steam_grade=self.steam_grade,
- steam_date=self.steam_date.isoformat() if self.steam_date else datetime.min.replace(tzinfo=timezone.utc),
- steam_shortcut=self.steam_shortcut,
- tags=self.tags,
- )
+ @property
+ def __dict__(self):
+ return dict(
+ queued=self.queued,
+ queue_pos=self.queue_pos,
+ last_played=self.last_played.isoformat() if self.last_played else datetime.min.replace(tzinfo=timezone.utc),
+ achievements_date=self.last_played.isoformat() if self.achievements_date else datetime.min.replace(
+ tzinfo=timezone.utc),
+ grant_date=self.grant_date.isoformat() if self.grant_date else datetime.min.replace(tzinfo=timezone.utc),
+ steam_appid=str(self.steam_appid) if self.steam_appid else None,
+ steam_grade=self.steam_grade,
+ steam_date=self.steam_date.isoformat() if self.steam_date else datetime.min.replace(tzinfo=timezone.utc),
+ steam_shortcut=self.steam_shortcut,
+ tags=self.tags,
+ )
- def __bool__(self):
- return self.queued or self.queue_pos is not None or self.last_played is not None
+ def __bool__(self):
+ return self.queued or self.queue_pos is not None or self.last_played is not None
+
+
+class RareGame(RareGameSlim):
def __init__(
self,
@@ -97,7 +100,7 @@ def __init__(
self.game.app_title += f" {self.game.app_name.split('_')[-1]}"
self.has_pixmap: bool = False
- self.metadata: RareGame.Metadata = RareGame.Metadata()
+ self.metadata: RareGameMetadata = RareGameMetadata()
self.__load_metadata()
self.grant_date()
@@ -109,8 +112,8 @@ def __init__(
self.__worker: Optional[QRunnable] = None
self.progress: int = 0
- self.signals.progress.start.connect(lambda: self.__on_progress_update(0))
- self.signals.progress.update.connect(self.__on_progress_update)
+ self.signals.progress.start.connect(self.__on_progress_update)
+ self.signals.progress.refresh.connect(self.__on_progress_update)
self.__steam_grade_pending: bool = False
self.game_process = GameProcess(self.game)
@@ -125,7 +128,7 @@ def __init__(
def add_dlc(self, dlc) -> None:
# lk: plug dlc progress signals to the game's
dlc.signals.progress.start.connect(self.signals.progress.start)
- dlc.signals.progress.update.connect(self.signals.progress.update)
+ dlc.signals.progress.refresh.connect(self.signals.progress.refresh)
dlc.signals.progress.finish.connect(self.signals.progress.finish)
dlc.parent_rgame = self
self.owned_dlcs.add(dlc)
@@ -139,19 +142,27 @@ def parent_rgame(self, rgame: "RareGame") -> None:
if self.is_dlc:
self.__parent_rgame = rgame
- def __on_progress_update(self, progress: int):
+ @Slot()
+ @Slot(int)
+ def __on_progress_update(self, progress: int = 0):
self.progress = progress
def get_worker(self) -> Optional[QRunnable]:
return self.__worker
- def set_worker(self, worker: Optional[QRunnable]):
+ @Slot(object)
+ def set_worker(self, worker: QRunnable):
if worker and self.__worker is not None:
- self.logger.error("Game '%s' already has attached worker %s", self.app_title, self.__worker)
+ self.logger.error("Game '%s' already has attached worker '%s'", self.app_title, str(self.__worker))
raise RuntimeError
+ worker.feedback.finished.connect(self.del_worker)
self.__worker = worker
- if worker is None:
- self.state = RareGame.State.IDLE
+
+ @Slot(object)
+ def del_worker(self, worker: QRunnable):
+ self.logger.debug("Removing worker '%s' from '%s'", str(worker), self.app_title)
+ self.__worker = None
+ self.state = RareGame.State.IDLE
@Slot(int)
def __game_launched(self, code: int):
@@ -195,7 +206,7 @@ def __load_metadata(self):
# pylint: disable=unsupported-membership-test
if self.app_name in metadata:
# pylint: disable=unsubscriptable-object
- self.metadata = RareGame.Metadata.from_dict(metadata[self.app_name])
+ self.metadata = RareGameMetadata.from_dict(metadata[self.app_name])
def __save_metadata(self):
with RareGame.__metadata_lock:
@@ -467,7 +478,7 @@ def save_path(self, path: str) -> None:
if self.igame and (self.game.supports_cloud_saves or self.game.supports_mac_cloud_saves):
self.igame.save_path = path
self.store_igame()
- self.signals.widget.update.emit()
+ self.signals.widget.refresh.emit()
@property
def achievements(self) -> Optional[Namespace]:
@@ -515,7 +526,7 @@ def eulas(self) -> List:
def reset_steam_date(self):
self.metadata.steam_date = datetime.min.replace(tzinfo=timezone.utc)
- self.signals.widget.update.emit()
+ self.signals.widget.refresh.emit()
@property
def steam_appid(self) -> Optional[str]:
@@ -552,7 +563,7 @@ def set_steam_grade(self) -> None:
self.metadata.steam_date = datetime.now(timezone.utc)
self.__steam_grade_pending = False
self.__save_metadata()
- self.signals.widget.update.emit()
+ self.signals.widget.refresh.emit()
def grant_date(self, force=False) -> datetime:
if not (entitlements := self.core.lgd.entitlements):
@@ -606,7 +617,7 @@ def get_pixmap(self, preset: ImageSize.Preset, color=True) -> QPixmap:
def __update_pixmap(self):
self.has_pixmap = self.image_manager.has_pixmaps(self.app_name)
if self.has_pixmap:
- self.signals.widget.update.emit()
+ self.signals.widget.refresh.emit()
def load_pixmaps(self):
"""Do not call this function, call set_pixmap instead. This is only used for initial image loading"""
@@ -758,7 +769,7 @@ def __init__(
def __update_pixmap(self):
self.has_pixmap = self.image_manager.has_pixmaps(self.app_name)
if self.has_pixmap:
- self.signals.widget.update.emit()
+ self.signals.widget.refresh.emit()
@property
def is_installed(self) -> bool:
@@ -863,3 +874,6 @@ def uninstall(self) -> bool:
keep_overlay_keys=platform.system() not in {"Windows"},
))
return True
+
+
+__all__ = ["RareGame", "RareEosOverlay"]
\ No newline at end of file
diff --git a/rare/models/base_game.py b/rare/models/game_slim.py
similarity index 91%
rename from rare/models/base_game.py
rename to rare/models/game_slim.py
index dbce38ea5d..1c4050e96f 100644
--- a/rare/models/base_game.py
+++ b/rare/models/game_slim.py
@@ -24,41 +24,50 @@ class RareSaveGame:
description: Optional[str] = ""
+class RareGameSignalsProgress(QObject):
+ start = Signal()
+ refresh = Signal(int)
+ finish = Signal(bool)
+
+
+class RareGameSignalsWidget(QObject):
+ refresh = Signal()
+
+
+class RareGameSignalsDownload(QObject):
+ enqueue = Signal(str)
+ dequeue = Signal(str)
+
+
+class RareGameSignalsGame(QObject):
+ install = Signal(InstallOptionsModel)
+ installed = Signal(str)
+ uninstall = Signal(UninstallOptionsModel)
+ uninstalled = Signal(str)
+ launched = Signal(str)
+ finished = Signal(str)
+
+
class RareGameSignals(QObject):
- class Progress(QObject):
- start = Signal()
- update = Signal(int)
- finish = Signal(bool)
-
- class Widget(QObject):
- update = Signal()
-
- class Download(QObject):
- enqueue = Signal(str)
- dequeue = Signal(str)
-
- class Game(QObject):
- install = Signal(InstallOptionsModel)
- installed = Signal(str)
- uninstall = Signal(UninstallOptionsModel)
- uninstalled = Signal(str)
- launched = Signal(str)
- finished = Signal(str)
def __init__(self, /):
super(RareGameSignals, self).__init__()
- self.progress = RareGameSignals.Progress()
- self.widget = RareGameSignals.Widget()
- self.download = RareGameSignals.Download()
- self.game = RareGameSignals.Game()
+ self.progress = RareGameSignalsProgress()
+ self.widget = RareGameSignalsWidget()
+ self.download = RareGameSignalsDownload()
+ self.game = RareGameSignalsGame()
def deleteLater(self):
+ self.progress.disconnect(self.progress)
self.progress.deleteLater()
del self.progress
+ self.widget.disconnect(self.widget)
self.widget.deleteLater()
del self.widget
+ self.download.disconnect(self.download)
self.download.deleteLater()
del self.download
+ self.game.disconnect(self.game)
self.game.deleteLater()
del self.game
super(RareGameSignals, self).deleteLater()
@@ -84,6 +93,7 @@ def __init__(self, legendary_core: LegendaryCore, game: Game):
self._state = RareGameBase.State.IDLE
def deleteLater(self):
+ self.signals.disconnect(self.signals)
self.signals.deleteLater()
del self.signals
super(RareGameBase, self).deleteLater()
@@ -96,7 +106,7 @@ def state(self) -> "RareGameBase.State":
def state(self, state: "RareGameBase.State"):
if state != self._state:
self._state = state
- self.signals.widget.update.emit()
+ self.signals.widget.refresh.emit()
@property
def is_idle(self):
@@ -324,7 +334,7 @@ def load_saves(self, saves: List[SaveGameFile]):
dt_remote=save.datetime,
)
self.saves.append(rsave)
- self.signals.widget.update.emit()
+ self.signals.widget.refresh.emit()
def update_saves(self):
"""Use only in a thread"""
diff --git a/rare/models/signals.py b/rare/models/signals.py
index 1e65b19513..bed9c8ae6e 100644
--- a/rare/models/signals.py
+++ b/rare/models/signals.py
@@ -3,59 +3,68 @@
from .install import InstallOptionsModel, UninstallOptionsModel
+class GlobalSignalsApplicationSignals(QObject):
+ # int: exit code
+ quit = Signal(int)
+ # str: title, str: body
+ notify = Signal(str, str)
+ # none
+ update_tray = Signal()
+ # none
+ update_statusbar = Signal()
+ # str: locale
+ # change_translation = Signal(str)
+ # none
+ update_game_tags = Signal()
+
+
+class GlobalSignalsGameSignals(QObject):
+ # model
+ install = Signal(InstallOptionsModel)
+ # str: app_name
+ installed = Signal(str)
+ # model
+ uninstall = Signal(UninstallOptionsModel)
+ # str: app_name
+ uninstalled = Signal(str)
+
+
+class GlobalSignalsDownloadSignals(QObject):
+ # str: app_name
+ enqueue = Signal(str)
+ # str: app_name
+ dequeue = Signal(str)
+
+
+class GlobalSignalsDiscordRPCSignals(QObject):
+ # str: app_name
+ update_presence = Signal(str)
+ # str: app_name
+ remove_presence = Signal(str)
+ # none
+ update_settings = Signal()
+
+
class GlobalSignals(QObject):
- class ApplicationSignals(QObject):
- # int: exit code
- quit = Signal(int)
- # str: title, str: body
- notify = Signal(str, str)
- # none
- update_tray = Signal()
- # none
- update_statusbar = Signal()
- # str: locale
- # change_translation = Signal(str)
- # none
- update_game_tags = Signal()
-
- class GameSignals(QObject):
- # model
- install = Signal(InstallOptionsModel)
- # str: app_name
- installed = Signal(str)
- # model
- uninstall = Signal(UninstallOptionsModel)
- # str: app_name
- uninstalled = Signal(str)
-
- class DownloadSignals(QObject):
- # str: app_name
- enqueue = Signal(str)
- # str: app_name
- dequeue = Signal(str)
-
- class DiscordRPCSignals(QObject):
- # str: app_name
- update_presence = Signal(str)
- # str: app_name
- remove_presence = Signal(str)
- # none
- update_settings = Signal()
def __init__(self):
super(GlobalSignals, self).__init__()
- self.application = GlobalSignals.ApplicationSignals()
- self.game = GlobalSignals.GameSignals()
- self.download = GlobalSignals.DownloadSignals()
- self.discord_rpc = GlobalSignals.DiscordRPCSignals()
+ self.application = GlobalSignalsApplicationSignals()
+ self.game = GlobalSignalsGameSignals()
+ self.download = GlobalSignalsDownloadSignals()
+ self.discord_rpc = GlobalSignalsDiscordRPCSignals()
def deleteLater(self):
+ self.application.disconnect(self.application)
self.application.deleteLater()
del self.application
+ self.game.disconnect(self.game)
self.game.deleteLater()
del self.game
+ self.download.disconnect(self.download)
self.download.deleteLater()
del self.download
+ self.discord_rpc.disconnect(self.discord_rpc)
self.discord_rpc.deleteLater()
del self.discord_rpc
super(GlobalSignals, self).deleteLater()
diff --git a/rare/shared/image_manager.py b/rare/shared/image_manager.py
index 2371749938..388414a931 100644
--- a/rare/shared/image_manager.py
+++ b/rare/shared/image_manager.py
@@ -67,6 +67,8 @@ def __init__(self, func: Callable[[Game, bool], None], game: Game, force: bool):
def run(self):
self.func(self.game, self.force)
self.signals.completed.emit(self.game)
+ self.signals.disconnect(self.signals)
+ self.signals.deleteLater()
class ImageManager(QObject):
diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py
index 6a9ee74a41..0456029cf3 100644
--- a/rare/shared/rare_core.py
+++ b/rare/shared/rare_core.py
@@ -12,8 +12,8 @@
from requests.exceptions import ConnectionError, HTTPError
from rare.lgndr.core import LegendaryCore
-from rare.models.base_game import RareSaveGame
from rare.models.game import RareEosOverlay, RareGame
+from rare.models.game_slim import RareSaveGame
from rare.models.settings import RareAppSettings
from rare.models.signals import GlobalSignals
from rare.utils import config_helper, steam_shortcuts
@@ -90,30 +90,34 @@ def __init__(self, settings: RareAppSettings, args: Namespace):
def enqueue_worker(self, rgame: RareGame, worker: QueueWorker):
rgame.set_worker(worker)
worker.feedback.started.connect(self.__signals.application.update_statusbar)
- worker.feedback.finished.connect(lambda: rgame.set_worker(None))
# signals are serviced in the order they are connected, so we have to
# connect the signal to update the statusbar after the one to remove the worker
# from the corresponding list
+ worker.feedback.finished.connect(self._on_worker_finished)
+ worker.feedback.finished.connect(self.__signals.application.update_statusbar)
+
if isinstance(worker, CloudSyncWorker):
- worker.feedback.finished.connect(lambda: self.workers_net.remove(worker))
- worker.feedback.finished.connect(self.__signals.application.update_statusbar)
self.workers_net.append(worker)
self.threadpool_net.start(worker, priority=0)
elif isinstance(worker, (VerifyWorker, MoveWorker)):
- worker.feedback.finished.connect(lambda: self.workers_disk.remove(worker))
- worker.feedback.finished.connect(self.__signals.application.update_statusbar)
self.workers_disk.append(worker)
self.threadpool_disk.start(worker, priority=0)
else:
raise RuntimeError(f"Cannot enqueue unkown worker type {type(worker).__name__}")
self.__signals.application.update_statusbar.emit()
+ def _on_worker_finished(self, worker: QueueWorker):
+ if worker in self.workers_disk:
+ self.workers_disk.remove(worker)
+ if worker in self.workers_net:
+ self.workers_net.remove(worker)
+
def dequeue_worker(self, worker: QueueWorker):
rgame = self.__library[worker.worker_info().app_name]
- rgame.set_worker(None)
- if worker in self.threadpool_disk:
+ rgame.del_worker(worker)
+ if worker in self.workers_disk:
self.workers_disk.remove(worker)
- if worker in self.threadpool_net:
+ if worker in self.workers_net:
self.workers_net.remove(worker)
self.__signals.application.update_statusbar.emit()
@@ -250,6 +254,7 @@ def deleteLater(self) -> None:
self.__eos_overlay = None
for rgame in self.__instance.games_and_dlcs:
+ rgame.disconnect(rgame)
rgame.deleteLater()
RareCore.__instance = None
super(RareCore, self).deleteLater()
diff --git a/rare/shared/workers/cloud_sync.py b/rare/shared/workers/cloud_sync.py
index 40751ca087..2fd9c2d4df 100644
--- a/rare/shared/workers/cloud_sync.py
+++ b/rare/shared/workers/cloud_sync.py
@@ -2,7 +2,7 @@
from PySide6.QtCore import QObject, Signal
-from rare.models.base_game import RareGameSlim
+from rare.models.game_slim import RareGameSlim
from .worker import QueueWorker, QueueWorkerInfo
diff --git a/rare/shared/workers/install.py b/rare/shared/workers/install.py
index 31e2b73056..35b715f9ce 100644
--- a/rare/shared/workers/install.py
+++ b/rare/shared/workers/install.py
@@ -12,15 +12,17 @@
from .worker import Worker
+class InstallInfoWorkerSignals(QObject):
+ result = Signal(InstallDownloadModel)
+ failed = Signal(str)
+ finished = Signal()
+
+
class InstallInfoWorker(Worker):
- class Signals(QObject):
- result = Signal(InstallDownloadModel)
- failed = Signal(str)
- finished = Signal()
def __init__(self, core: LegendaryCore, options: InstallOptionsModel):
super(InstallInfoWorker, self).__init__()
- self.signals = InstallInfoWorker.Signals()
+ self.signals = InstallInfoWorkerSignals()
self.core = core
self.options = options
diff --git a/rare/shared/workers/move.py b/rare/shared/workers/move.py
index 04cbf65a61..33ccd0be2d 100644
--- a/rare/shared/workers/move.py
+++ b/rare/shared/workers/move.py
@@ -25,13 +25,15 @@ class MovePathEditReasons(IndicatorReasons):
MOVEDIALOG_NO_SPACE = auto()
+class MoveInfoWorkerSignals(QObject):
+ result: Signal = Signal(bool, object, object, MovePathEditReasons)
+
+
class MoveInfoWorker(Worker):
- class Signals(QObject):
- result: Signal = Signal(bool, object, object, MovePathEditReasons)
def __init__(self, rgame: RareGame, igames: Iterator[RareGame], options: MoveGameModel):
super(MoveInfoWorker, self).__init__()
- self.signals = MoveInfoWorker.Signals()
+ self.signals = MoveInfoWorkerSignals()
self.rgame: RareGame = rgame
self.installed_games: Iterator[RareGame] = igames
@@ -131,7 +133,7 @@ def worker_info(self) -> QueueWorkerInfo:
def progress(self, src_size, dst_size):
progress = dst_size * 100 // src_size
- self.rgame.signals.progress.update.emit(progress)
+ self.rgame.signals.progress.refresh.emit(progress)
self.signals.progress.emit(self.rgame, progress, src_size, dst_size)
def run_real(self):
diff --git a/rare/shared/workers/uninstall.py b/rare/shared/workers/uninstall.py
index 5407d6edd8..61ea760958 100644
--- a/rare/shared/workers/uninstall.py
+++ b/rare/shared/workers/uninstall.py
@@ -95,13 +95,15 @@ def uninstall_game(
return status.success, status.message
+class UninstallWorkerSignals(QObject):
+ result = Signal(RareGame, bool, str)
+
+
class UninstallWorker(Worker):
- class Signals(QObject):
- result = Signal(RareGame, bool, str)
def __init__(self, core: LegendaryCore, rgame: RareGame, options: UninstallOptionsModel):
super(UninstallWorker, self).__init__()
- self.signals = UninstallWorker.Signals()
+ self.signals = UninstallWorkerSignals()
self.core = core
self.rgame = rgame
self.options = options
diff --git a/rare/shared/workers/verify.py b/rare/shared/workers/verify.py
index 01e0732e0d..b82305c9fa 100644
--- a/rare/shared/workers/verify.py
+++ b/rare/shared/workers/verify.py
@@ -31,7 +31,7 @@ def __init__(self, core: LegendaryCore, args: Namespace, rgame: RareGame):
self.rgame.state = RareGame.State.VERIFYING
def __status_callback(self, num: int, total: int, percentage: float, speed: float):
- self.rgame.signals.progress.update.emit(num * 100 // total)
+ self.rgame.signals.progress.refresh.emit(num * 100 // total)
self.signals.progress.emit(self.rgame, num, total, percentage, speed)
def worker_info(self) -> QueueWorkerInfo:
diff --git a/rare/shared/workers/wine_resolver.py b/rare/shared/workers/wine_resolver.py
index 967f2f64a9..c6bc627231 100644
--- a/rare/shared/workers/wine_resolver.py
+++ b/rare/shared/workers/wine_resolver.py
@@ -25,13 +25,15 @@
from rare.utils.compat import utils as compat_utils
+class WinePathResolverSignals(QObject):
+ result_ready = Signal(str, str)
+
+
class WinePathResolver(Worker):
- class Signals(QObject):
- result_ready = Signal(str, str)
def __init__(self, core: LegendaryCore, app_name: str, path: str):
super(WinePathResolver, self).__init__()
- self.signals = WinePathResolver.Signals()
+ self.signals = WinePathResolverSignals()
self.core = core
self.app_name = app_name
self.path = path
diff --git a/rare/shared/workers/worker.py b/rare/shared/workers/worker.py
index 1ddb6e35b9..1d12651a3a 100644
--- a/rare/shared/workers/worker.py
+++ b/rare/shared/workers/worker.py
@@ -2,7 +2,6 @@
from dataclasses import dataclass
from enum import IntEnum
from logging import Logger, getLogger
-from typing import Optional
from PySide6.QtCore import QObject, QRunnable, Signal, Slot
@@ -21,7 +20,10 @@ def __init__(self):
super(Worker, self).__init__()
self.setAutoDelete(True)
self.__logger = getLogger(type(self).__name__)
- self.__signals: Optional[QObject] = None
+ self.__signals: QObject = None
+
+ def __str__(self):
+ return type(self).__name__
@property
def logger(self) -> Logger:
@@ -44,6 +46,7 @@ def run_real(self):
@Slot()
def run(self):
self.run_real()
+ self.signals.disconnect(self.signals)
self.signals.deleteLater()
@@ -63,6 +66,13 @@ class QueueWorkerInfo:
progress: int = 0
+class QueueWorkerSignals(QObject):
+ # object: worker object
+ started = Signal(object)
+ # object: worker object
+ finished = Signal(object)
+
+
class QueueWorker(Worker):
"""
Base queueable worker class
@@ -74,22 +84,20 @@ class QueueWorker(Worker):
to the `QueueWorker.signals` attribute, implement `QueueWorker.run_real()` and `QueueWorker.worker_info()`
"""
- class Signals(QObject):
- started = Signal()
- finished = Signal()
-
def __init__(self):
super(QueueWorker, self).__init__()
- self.feedback = QueueWorker.Signals()
+ self.feedback = QueueWorkerSignals()
self.state = QueueWorkerState.QUEUED
self._kill = False
@Slot()
def run(self):
self.state = QueueWorkerState.ACTIVE
- self.feedback.started.emit()
+ self.feedback.started.emit(self)
super(QueueWorker, self).run()
- self.feedback.finished.emit()
+ self.feedback.finished.emit(self)
+ self.feedback.started.disconnect()
+ self.feedback.finished.disconnect()
self.feedback.deleteLater()
@abstractmethod
diff --git a/rare/ui/components/tabs/settings/about.py b/rare/ui/components/tabs/settings/about.py
index abb5a9d050..37a7fa73a4 100644
--- a/rare/ui/components/tabs/settings/about.py
+++ b/rare/ui/components/tabs/settings/about.py
@@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'about.ui'
##
-## Created by: Qt User Interface Compiler version 6.9.1
+## Created by: Qt User Interface Compiler version 6.10.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -17,10 +17,10 @@ class Ui_About(object):
def setupUi(self, About):
if not About.objectName():
About.setObjectName(u"About")
- About.resize(507, 210)
+ About.resize(508, 210)
self.about_layout = QFormLayout(About)
self.about_layout.setObjectName(u"about_layout")
- self.about_layout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+ self.about_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.version_label = QLabel(About)
self.version_label.setObjectName(u"version_label")
font = QFont()
@@ -41,11 +41,11 @@ def setupUi(self, About):
self.about_layout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.update_label)
- self.update_lbl = QLabel(About)
- self.update_lbl.setObjectName(u"update_lbl")
- self.update_lbl.setText(u"error")
+ self.update_field = QLabel(About)
+ self.update_field.setObjectName(u"update_field")
+ self.update_field.setText(u"error")
- self.about_layout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.update_lbl)
+ self.about_layout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.update_field)
self.open_browser = QPushButton(About)
self.open_browser.setObjectName(u"open_browser")
diff --git a/rare/ui/components/tabs/settings/about.ui b/rare/ui/components/tabs/settings/about.ui
index 967d702965..f2d797174f 100644
--- a/rare/ui/components/tabs/settings/about.ui
+++ b/rare/ui/components/tabs/settings/about.ui
@@ -6,7 +6,7 @@
0
0
- 507
+ 508
210
@@ -15,13 +15,12 @@
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
-
- 75
true
@@ -41,7 +40,6 @@
- 75
true
@@ -51,7 +49,7 @@
-
-
+
error
@@ -74,7 +72,6 @@
- 75
true
@@ -102,7 +99,6 @@
- 75
true
@@ -128,7 +124,6 @@
- 75
true
@@ -148,7 +143,6 @@
- 75
true
diff --git a/rare/utils/compat/steam.py b/rare/utils/compat/steam.py
index 9a32962ec8..9258c7ecd9 100644
--- a/rare/utils/compat/steam.py
+++ b/rare/utils/compat/steam.py
@@ -8,6 +8,8 @@
import vdf
+from rare.utils.paths import data_dir
+
logger = getLogger("SteamTools")
steam_client_install_paths = [os.path.expanduser("~/.local/share/Steam")]
@@ -254,10 +256,12 @@ def find_steam_tools(steam_path: str, library: str) -> List[ProtonTool]:
def find_compatibility_tools(steam_path: str) -> List[CompatibilityTool]:
compatibilitytools_paths = {
- "/usr/share/steam/compatibilitytools.d",
+ data_dir().joinpath("compatibilitytools").as_posix(),
+ os.path.expanduser("~/.local/share/umu/compatibilitytools"),
os.path.expanduser(os.path.join(steam_path, "compatibilitytools.d")),
os.path.expanduser("~/.steam/compatibilitytools.d"),
os.path.expanduser("~/.steam/root/compatibilitytools.d"),
+ "/usr/share/steam/compatibilitytools.d",
}
compatibilitytools_paths = {os.path.realpath(path) for path in compatibilitytools_paths if os.path.isdir(path)}
tools = []
diff --git a/rare/utils/compat/wine.py b/rare/utils/compat/wine.py
index 998b142ba4..c92189f4b5 100644
--- a/rare/utils/compat/wine.py
+++ b/rare/utils/compat/wine.py
@@ -19,6 +19,7 @@ def find_lutris() -> Tuple[str, str]:
if os.path.isdir(path) and os.path.isdir(runtime_path) and os.path.isdir(wine_path):
__lutris_runtime, __lutris_wine = runtime_path, wine_path
return runtime_path, wine_path
+ return "", ""
@dataclass
@@ -40,6 +41,7 @@ def find_lutris_wines(runtime_path: str = None, wine_path: str = None) -> List[W
runners = []
if not runtime_path and not wine_path:
return runners
+ return runners
def __get_lib_path(executable: str, basename: str = "") -> str:
diff --git a/rare/utils/misc.py b/rare/utils/misc.py
index e81cb4bda4..97cd661216 100644
--- a/rare/utils/misc.py
+++ b/rare/utils/misc.py
@@ -1,3 +1,4 @@
+import functools
import os
from datetime import UTC, datetime
from enum import IntEnum
@@ -214,6 +215,16 @@ def qta_icon(icn_str: str, fallback: str = None, **kwargs):
return qtawesome.icon("ei.error", **kwargs)
+# Source - https://stackoverflow.com/a
+# Posted by benrg
+# Retrieved 2025-12-25, License - CC BY-SA 4.0
+
+def partial_bound_method(bound_method, *args, **kwargs):
+ f = functools.partialmethod(bound_method.__func__, *args, **kwargs)
+ # NB: the seemingly redundant lambda is needed to ensure the correct result type
+ return (lambda *args: f(*args)).__get__(bound_method.__self__)
+
+
def widget_object_name(widget: Union[QObject, ShibokenObject, Type], suffix: str) -> str:
suffix = f"_{suffix}" if suffix else ""
if isinstance(widget, QObject):
diff --git a/rare/utils/qt_requests.py b/rare/utils/qt_requests.py
index a9a0431592..74b6bb7700 100644
--- a/rare/utils/qt_requests.py
+++ b/rare/utils/qt_requests.py
@@ -121,9 +121,7 @@ def __on_finished(self, reply: QNetworkReply):
item = self.__active_requests.pop(reply, None)
if item is None:
self.logger.error("QNetworkReply: %s without associated item", reply.url().toString())
- reply.deleteLater()
- return
- if reply.error() != QNetworkReply.NetworkError.NoError:
+ elif reply.error() != QNetworkReply.NetworkError.NoError:
self.logger.error(reply.errorString())
else:
mimetype, charset = self.__parse_content_type(reply.header(QNetworkRequest.KnownHeaders.ContentTypeHeader))
@@ -137,4 +135,5 @@ def __on_finished(self, reply: QNetworkReply):
data = None
for handler in item.handlers:
handler(data)
+ reply.disconnect(reply)
reply.deleteLater()
diff --git a/rare/utils/slot_adapters.py b/rare/utils/slot_adapters.py
new file mode 100644
index 0000000000..3e97ca2b9c
--- /dev/null
+++ b/rare/utils/slot_adapters.py
@@ -0,0 +1,72 @@
+import types
+from typing import Callable
+
+from PySide6 import QtCore, QtGui, QtWidgets
+
+
+class CallableSlotAdapter(QtCore.QObject):
+ """A QObject that calls a python callable whenever its slot is called.
+
+ :param parent: The required parent QObject that manages this
+ instance lifecycle through the Qt parent-child relationship.
+ :param fn: The Python callable to call.
+
+ Connect the desired signal to self.slot.
+ """
+
+ def __init__(self, parent, fn):
+ self._fn = fn
+ super().__init__(parent)
+
+ def slot(self, *args):
+ code = self._fn.__code__
+ co_argcount = code.co_argcount
+ if args and isinstance(self._fn, types.MethodType):
+ args -= 1
+ self._fn(*args[:co_argcount])
+
+
+class CallableAction(QtGui.QAction):
+ """An action that calls a python callable.
+
+ :param parent: The parent for this action. If parent is a QMenu instance,
+ or QActionGroup who has a QMenu instance parent,
+ then automatically call parent.addAction(self).
+ :param text: The text to display for the action.
+ :param fn: The callable that is called with each trigger.
+ :param checkable: True to allow checkable.
+ :param checked: Set the checked state, if checkable.
+
+ As of Feb 2024, connecting a callable to a signal increments
+ the callable's reference count. Unfortunately, this reference
+ count is not decremented when the object is deleted.
+
+ This class provides a Python-side wrapper for a QAction that
+ connects to a Python callable. You can safely provide a lambda
+ which will be dereferenced and deleted along with the QAction.
+
+ For a discussion on PySide6 memory management, see
+ https://forum.qt.io/topic/154590/pyside6-memory-model-and-qobject-lifetime-management/11
+ """
+
+ def __init__(self, parent: QtCore.QObject, text: str, fn: Callable, checkable=False, checked=False):
+ self._fn = fn
+ super().__init__(text, parent=parent)
+ if bool(checkable):
+ self.setCheckable(True)
+ self.setChecked(bool(checked))
+ self.triggered.connect(self._on_triggered)
+ while isinstance(parent, QtGui.QActionGroup):
+ parent = parent.parent()
+ if isinstance(parent, QtWidgets.QMenu):
+ parent.addAction(self)
+
+ def _on_triggered(self, checked=False):
+ code = self._fn.__code__
+ args = code.co_argcount
+ if args and isinstance(self._fn, types.MethodType):
+ args -= 1
+ if args == 0:
+ self._fn()
+ elif args == 1:
+ self._fn(checked)
diff --git a/rare/widgets/button_edit.py b/rare/widgets/button_edit.py
index e5362044da..505aff6ece 100644
--- a/rare/widgets/button_edit.py
+++ b/rare/widgets/button_edit.py
@@ -15,7 +15,7 @@ def __init__(self, icon_name, placeholder_text: str, parent=None):
self.button.setObjectName(f"{type(self).__name__}Button")
self.button.setIcon(qta_icon(icon_name))
self.button.setCursor(Qt.CursorShape.ArrowCursor)
- self.button.clicked.connect(self.buttonClicked.emit)
+ self.button.clicked.connect(self.buttonClicked)
self.setPlaceholderText(placeholder_text)
# frame_width = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
diff --git a/rare/widgets/image_widget.py b/rare/widgets/image_widget.py
index 2ad859060e..8315dc2038 100644
--- a/rare/widgets/image_widget.py
+++ b/rare/widgets/image_widget.py
@@ -29,16 +29,17 @@ class Border(Enum):
Rounded = 0
Squared = 1
- _pixmap: Optional[QPixmap] = None
- _opacity: float = 1.0
- _transform: QTransform
- _smooth_transform: bool = False
_rounded_overlay: Optional[OverlayPath] = None
_squared_overlay: Optional[OverlayPath] = None
- _image_size: Optional[ImageSize.Preset] = None
def __init__(self, parent=None) -> None:
super(ImageWidget, self).__init__(parent=parent)
+ self._pixmap: Optional[QPixmap] = None
+ self._opacity: float = 1.0
+ self._transform: QTransform = None
+ self._smooth_transform: bool = False
+ self._image_size: Optional[ImageSize.Preset] = None
+
self.setObjectName(type(self).__name__)
self.setContentsMargins(0, 0, 0, 0)
self.paint_image = self.paint_image_empty
@@ -187,8 +188,8 @@ def __init__(self, manager: QtRequests, parent=None):
self.spinner.setVisible(False)
def fetchPixmap(self, url: str, params: Dict = None):
- self.spinner.start()
self.spinner.setFixedSize(self._image_size.size)
+ self.spinner.start()
params = {
"resize": 1,
"w": self._image_size.base.size.width(),
diff --git a/rare/widgets/indicator_edit.py b/rare/widgets/indicator_edit.py
index 09a5525b63..69b3b07f8f 100644
--- a/rare/widgets/indicator_edit.py
+++ b/rare/widgets/indicator_edit.py
@@ -99,20 +99,22 @@ def extend(self, reasons: Dict):
self.__text.update(reasons)
-class EditFuncRunnable(QRunnable):
- class Signals(QObject):
- result = Signal(bool, str, int)
+class EditFuncRunnableSignals(QObject):
+ result = Signal(bool, str, int)
+
+class EditFuncRunnable(QRunnable):
def __init__(self, func: Callable[[str], Tuple[bool, str, int]], args: str):
super(EditFuncRunnable, self).__init__()
self.setAutoDelete(True)
- self.signals = EditFuncRunnable.Signals()
+ self.signals = EditFuncRunnableSignals()
self.func = self.__wrap_edit_function(func)
self.args = args
def run(self):
o0, o1, o2 = self.func(self.args)
self.signals.result.emit(o0, o1, o2)
+ self.signals.disconnect(self.signals)
self.signals.deleteLater()
@staticmethod