From 9b10a487043fc556756368702abccd86c2aaacd7 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Thu, 18 Jan 2024 16:29:52 +0200
Subject: [PATCH 01/29] ImageManager: Reduce thread count even more
---
rare/shared/image_manager.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rare/shared/image_manager.py b/rare/shared/image_manager.py
index e3085d8d..3a2ba479 100644
--- a/rare/shared/image_manager.py
+++ b/rare/shared/image_manager.py
@@ -82,7 +82,7 @@ class ImageManager(QObject):
self.device = ImageSize.Preset(1, QApplication.instance().devicePixelRatio())
self.threadpool = QThreadPool()
- self.threadpool.setMaxThreadCount(6)
+ self.threadpool.setMaxThreadCount(4)
def __img_dir(self, app_name: str) -> Path:
return self.image_dir.joinpath(app_name)
From 446cbd6c67df5decae3f55ecd3f99365ee63c572 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Thu, 18 Jan 2024 16:25:56 +0200
Subject: [PATCH 02/29] Lgdnr: Check for old_igame before writing tags
---
rare/lgndr/cli.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rare/lgndr/cli.py b/rare/lgndr/cli.py
index d0a4e8d2..f8263732 100644
--- a/rare/lgndr/cli.py
+++ b/rare/lgndr/cli.py
@@ -341,7 +341,7 @@ class LegendaryCLI(LegendaryCLIReal):
self.core.uninstall_tag(old_igame)
self.core.install_game(old_igame)
- if old_igame.install_tags:
+ if old_igame and old_igame.install_tags:
self.core.lgd.config.set(game.app_name, 'install_tags', ','.join(old_igame.install_tags))
self.core.lgd.save_config()
From 4e2ffaae93ccd3db5a8f163320fe52d24ece0847 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Thu, 11 Jan 2024 21:51:01 +0200
Subject: [PATCH 03/29] ImageSize: Use pixel ratio 1 for image downloads
---
rare/models/image.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rare/models/image.py b/rare/models/image.py
index 68a87c9e..c4145c08 100644
--- a/rare/models/image.py
+++ b/rare/models/image.py
@@ -58,10 +58,10 @@ class ImageSize:
def base(self) -> 'ImageSize.Preset':
return self.__base
- Image = Preset(1, 2)
+ Image = Preset(1, 1)
"""! @brief Size and pixel ratio of the image on disk"""
- ImageWide = Preset(1, 2, Orientation.Wide)
+ ImageWide = Preset(1, 1, Orientation.Wide)
"""! @brief Size and pixel ratio for wide 16/9 image on disk"""
Display = Preset(1, 1, base=Image)
From 50276384cc78d7b6d619858ef6a923bc2b296102 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Thu, 11 Jan 2024 21:37:04 +0200
Subject: [PATCH 04/29] QtRequests: Use QNetworkAccessManager's queue
---
rare/utils/qt_requests.py | 29 +++++------------------------
1 file changed, 5 insertions(+), 24 deletions(-)
diff --git a/rare/utils/qt_requests.py b/rare/utils/qt_requests.py
index 23f4dfc9..ebd1003f 100644
--- a/rare/utils/qt_requests.py
+++ b/rare/utils/qt_requests.py
@@ -8,7 +8,6 @@ import orjson
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QNetworkDiskCache
-REQUEST_LIMIT = 8
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"
RequestHandler = TypeVar("RequestHandler", bound=Callable[[Union[Dict, bytes]], None])
@@ -33,7 +32,6 @@ class QtRequests(QObject):
self.log = getLogger(f"{type(self).__name__}_{type(parent).__name__}")
self.manager = QNetworkAccessManager(self)
self.manager.finished.connect(self.__on_finished)
- self.manager.finished.connect(self.__process_next)
self.cache = None
if cache is not None:
self.log.debug("Using cache dir %s", cache)
@@ -44,8 +42,7 @@ class QtRequests(QObject):
self.log.debug("Manager is authorized")
self.token = token
- self.__pending_requests = []
- self.__active_requests = {}
+ self.__active_requests: Dict[QNetworkReply, RequestQueueItem] = {}
@staticmethod
def __prepare_query(url, params) -> QUrl:
@@ -58,7 +55,7 @@ class QtRequests(QObject):
def __prepare_request(self, item: RequestQueueItem) -> QNetworkRequest:
request = QNetworkRequest(item.url)
- request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json;charset=UTF-8")
+ request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json; charset=UTF-8")
request.setHeader(QNetworkRequest.UserAgentHeader, USER_AGENT)
request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, QNetworkRequest.NoLessSafeRedirectPolicy)
if self.cache is not None:
@@ -76,10 +73,7 @@ class QtRequests(QObject):
def post(self, url: str, handler: RequestHandler, payload: dict):
item = RequestQueueItem(method="post", url=QUrl(url), payload=payload, handlers=[handler])
- if len(self.__active_requests) < REQUEST_LIMIT:
- self.__post(item)
- else:
- self.__pending_requests.append(item)
+ self.__post(item)
def __get(self, item: RequestQueueItem):
request = self.__prepare_request(item)
@@ -90,10 +84,7 @@ class QtRequests(QObject):
def get(self, url: str, handler: RequestHandler, payload: Dict = None, params: Dict = None):
url = self.__prepare_query(url, params) if params is not None else QUrl(url)
item = RequestQueueItem(method="get", url=url, payload=payload, handlers=[handler])
- if len(self.__active_requests) < REQUEST_LIMIT:
- self.__get(item)
- else:
- self.__pending_requests.append(item)
+ self.__get(item)
def __on_error(self, error: QNetworkReply.NetworkError) -> None:
self.log.error(error)
@@ -102,19 +93,9 @@ class QtRequests(QObject):
def __parse_content_type(header) -> Tuple[str, str]:
# lk: this looks weird but `cgi` is deprecated, PEP 594 suggests this way of parsing MIME
m = Message()
- m['content-type'] = header
+ m["content-type"] = header
return m.get_content_type(), m.get_content_charset()
- def __process_next(self):
- if self.__pending_requests:
- item = self.__pending_requests.pop(0)
- if item.method == "post":
- self.__post(item)
- elif item.method == "get":
- self.__get(item)
- else:
- raise NotImplementedError
-
@pyqtSlot(QNetworkReply)
def __on_finished(self, reply: QNetworkReply):
item = self.__active_requests.pop(reply, None)
From 9d3e9fecea52f0a4bc9ffd71b02f9163a0127b7d Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Tue, 9 Jan 2024 16:33:45 +0200
Subject: [PATCH 05/29] GameProcess: Add more information in the failure
message when a timeout occurs
---
rare/shared/game_process.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/rare/shared/game_process.py b/rare/shared/game_process.py
index b12b690a..07dbe2b1 100644
--- a/rare/shared/game_process.py
+++ b/rare/shared/game_process.py
@@ -64,7 +64,14 @@ class GameProcess(QObject):
self.tried_connections += 1
if self.tried_connections > 50: # 10 seconds
- QMessageBox.warning(None, "Error", self.tr("Connection to game process failed (Timeout)"))
+ QMessageBox.warning(
+ None,
+ self.tr("Error - {}").format(self.game.app_title),
+ self.tr(
+ "Connection to game launcher for {} failed due to timeout.\n"
+ "This is usually do it the game or Rare's game launcher already running"
+ ).format(self.game.app_name)
+ )
self.timer.stop()
self.finished.emit(GameProcess.Code.TIMEOUT)
From 971c31e8f42cb1cb89ac40af20a1ccebee90480f Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Tue, 9 Jan 2024 16:32:53 +0200
Subject: [PATCH 06/29] SelectiveWidget: Create specialized widget for
selecting optional downloads
Because there are two dialogs for editing optional downloads, refactor
the separate implementations to select them into a common reusable widget
and use it in SelectiveDialog and InstallDialog.
---
rare/components/dialogs/install_dialog.py | 125 ++++++++-----------
rare/components/dialogs/selective_dialog.py | 78 ++----------
rare/ui/components/dialogs/install_dialog.py | 2 +-
rare/ui/components/dialogs/install_dialog.ui | 4 +-
rare/widgets/collapsible_widget.py | 30 +++--
rare/widgets/selective_widget.py | 55 ++++++++
requirements-flatpak.txt | 2 +-
requirements-full.txt | 2 +-
requirements.txt | 2 +-
9 files changed, 139 insertions(+), 161 deletions(-)
create mode 100644 rare/widgets/selective_widget.py
diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py
index 49c5f10a..1f5828f7 100644
--- a/rare/components/dialogs/install_dialog.py
+++ b/rare/components/dialogs/install_dialog.py
@@ -6,8 +6,7 @@ from typing import Tuple, List, Union, Optional
from PyQt5.QtCore import QThreadPool, QSettings
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QShowEvent
-from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QVBoxLayout, QFormLayout
-from legendary.utils.selective_dl import get_sdl_appname
+from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QFormLayout
from rare.models.game import RareGame
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel
@@ -15,18 +14,46 @@ from rare.shared.workers.install_info import InstallInfoWorker
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
from rare.utils.misc import format_size, icon
-from rare.widgets.dialogs import ActionDialog, dialog_title_game
from rare.widgets.collapsible_widget import CollapsibleFrame
+from rare.widgets.dialogs import ActionDialog, dialog_title_game
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
+from rare.widgets.selective_widget import SelectiveWidget
class InstallDialogAdvanced(CollapsibleFrame):
def __init__(self, parent=None):
- widget = QWidget(parent)
- title = widget.tr("Advanced options")
+ super(InstallDialogAdvanced, self).__init__(parent=parent)
+
+ title = self.tr("Advanced options")
+ self.setTitle(title)
+
+ widget = QWidget(parent=self)
self.ui = Ui_InstallDialogAdvanced()
self.ui.setupUi(widget)
- super(InstallDialogAdvanced, self).__init__(widget=widget, title=title, parent=parent)
+ self.setWidget(widget)
+
+
+class InstallDialogSelective(CollapsibleFrame):
+ stateChanged: pyqtSignal = pyqtSignal()
+
+ def __init__(self, rgame: RareGame, parent=None):
+ super(InstallDialogSelective, self).__init__(parent=parent)
+ title = self.tr("Optional downloads")
+ self.setTitle(title)
+ self.setEnabled(bool(rgame.sdl_name))
+
+ self.selective_widget: SelectiveWidget = None
+ self.rgame = rgame
+
+ def update_list(self, platform: str):
+ if self.selective_widget is not None:
+ self.selective_widget.deleteLater()
+ self.selective_widget = SelectiveWidget(self.rgame, platform, parent=self)
+ self.selective_widget.stateChanged.connect(self.stateChanged)
+ self.setWidget(self.selective_widget)
+
+ def install_tags(self):
+ return self.selective_widget.install_tags()
class InstallDialog(ActionDialog):
@@ -64,7 +91,8 @@ class InstallDialog(ActionDialog):
self.advanced = InstallDialogAdvanced(parent=self)
self.ui.advanced_layout.addWidget(self.advanced)
- self.selectable = CollapsibleFrame(widget=None, title=self.tr("Optional downloads"), parent=self)
+ self.selectable = InstallDialogSelective(rgame, parent=self)
+ self.selectable.stateChanged.connect(self.option_changed)
self.ui.selectable_layout.addWidget(self.selectable)
self.options_changed = False
@@ -82,13 +110,14 @@ class InstallDialog(ActionDialog):
self.install_dir_edit = PathEdit(
path=base_path,
file_mode=QFileDialog.DirectoryOnly,
- edit_func=self.option_changed,
- save_func=self.save_install_edit,
+ edit_func=self.install_dir_edit_callback,
+ save_func=self.install_dir_save_callback,
parent=self,
)
self.ui.main_layout.setWidget(
self.ui.main_layout.getWidgetPosition(self.ui.install_dir_label)[0],
- QFormLayout.FieldRole, self.install_dir_edit
+ QFormLayout.FieldRole,
+ self.install_dir_edit,
)
self.install_dir_edit.setDisabled(rgame.is_installed)
@@ -102,10 +131,10 @@ class InstallDialog(ActionDialog):
self.ui.platform_combo.addItems(reversed(rgame.platforms))
combo_text = rgame.igame.platform if rgame.is_installed else rgame.default_platform
self.ui.platform_combo.setCurrentIndex(self.ui.platform_combo.findText(combo_text))
- self.ui.platform_combo.currentIndexChanged.connect(lambda i: self.option_changed(None))
+ self.ui.platform_combo.currentIndexChanged.connect(self.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.currentIndexChanged.connect(self.reset_sdl_list)
+ self.ui.platform_combo.currentTextChanged.connect(self.selectable.update_list)
self.ui.platform_label.setDisabled(rgame.is_installed)
self.ui.platform_combo.setDisabled(rgame.is_installed)
@@ -131,11 +160,8 @@ class InstallDialog(ActionDialog):
lambda: self.non_reload_option_changed("shortcut")
)
- self.selectable_checks: List[TagCheckBox] = []
- self.config_tags: Optional[List[str]] = None
-
self.reset_install_dir(self.ui.platform_combo.currentIndex())
- self.reset_sdl_list(self.ui.platform_combo.currentIndex())
+ self.selectable.update_list(self.ui.platform_combo.currentText())
self.check_incompatible_platform(self.ui.platform_combo.currentIndex())
self.accept_button.setEnabled(False)
@@ -180,7 +206,7 @@ class InstallDialog(ActionDialog):
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
- self.save_install_edit(self.install_dir_edit.text())
+ self.install_dir_save_callback(self.install_dir_edit.text())
super().showEvent(a0)
def execute(self):
@@ -197,68 +223,30 @@ class InstallDialog(ActionDialog):
default_dir = self.core.get_default_install_dir(platform)
self.install_dir_edit.setText(default_dir)
- @pyqtSlot(int)
- def reset_sdl_list(self, index: int):
- platform = self.ui.platform_combo.itemText(index)
- for cb in self.selectable_checks:
- cb.disconnect()
- cb.deleteLater()
- self.selectable_checks.clear()
-
- if config_tags := self.core.lgd.config.get(self.rgame.app_name, 'install_tags', fallback=None):
- self.config_tags = config_tags.split(",")
- config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, 'disable_sdl', fallback=False)
- sdl_name = get_sdl_appname(self.rgame.app_name)
- if not config_disable_sdl and sdl_name is not None:
- sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
- if sdl_data:
- widget = QWidget(self.selectable)
- layout = QVBoxLayout(widget)
- layout.setSpacing(0)
- for tag, info in sdl_data.items():
- cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
- if tag == "__required":
- cb.setChecked(True)
- cb.setDisabled(True)
- if self.config_tags is not None:
- if all(elem in self.config_tags for elem in info["tags"]):
- cb.setChecked(True)
- layout.addWidget(cb)
- self.selectable_checks.append(cb)
- for cb in self.selectable_checks:
- cb.stateChanged.connect(self.option_changed)
- self.selectable.setWidget(widget)
- else:
- self.selectable.setDisabled(True)
-
@pyqtSlot(int)
def check_incompatible_platform(self, index: int):
platform = self.ui.platform_combo.itemText(index)
if platform == "Mac" and pf.system() != "Darwin":
self.error_box(
self.tr("Warning"),
- self.tr("You will not be able to run the game if you select {} as platform").format(platform)
+ self.tr("You will not be able to run the game if you select {} as platform").format(platform),
)
else:
self.error_box()
def get_options(self):
self.__options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text()
+ self.__options.platform = self.ui.platform_combo.currentText()
+ self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
self.__options.max_workers = self.advanced.ui.max_workers_spin.value()
self.__options.shared_memory = self.advanced.ui.max_memory_spin.value()
self.__options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
self.__options.force = self.advanced.ui.force_download_check.isChecked()
self.__options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
self.__options.no_install = self.advanced.ui.download_only_check.isChecked()
- self.__options.platform = self.ui.platform_combo.currentText()
self.__options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
- self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
- if self.selectable_checks:
- self.__options.install_tag = [""]
- for cb in self.selectable_checks:
- if data := cb.isChecked():
- # noinspection PyTypeChecker
- self.__options.install_tag.extend(data)
+ self.__options.install_tag = self.selectable.install_tags()
+ self.__options.reset_sdl = True
def get_download_info(self):
self.__download = None
@@ -279,13 +267,17 @@ class InstallDialog(ActionDialog):
self.get_options()
self.get_download_info()
- def option_changed(self, path) -> Tuple[bool, str, int]:
+ @pyqtSlot()
+ def option_changed(self):
self.options_changed = True
self.accept_button.setEnabled(False)
self.action_button.setEnabled(not self.active())
+
+ def install_dir_edit_callback(self, path: str) -> Tuple[bool, str, int]:
+ self.option_changed()
return True, path, IndicatorReasonsCommon.VALID
- def save_install_edit(self, path: str):
+ def install_dir_save_callback(self, path: str):
if not os.path.exists(path):
return
_, _, free_space = shutil.disk_usage(path)
@@ -369,12 +361,6 @@ class InstallDialog(ActionDialog):
self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download)
def reject_handler(self):
- # FIXME: This is implemented through the selective downloads dialog now. remove soon
- # if self.config_tags is not None:
- # config_helper.set_option(self.rgame.app_name, 'install_tags', ','.join(self.config_tags))
- # else:
- # # lk: this is purely for cleaning any install tags we might have added erroneously to the config
- # config_helper.remove_option(self.rgame.app_name, 'install_tags')
self.__queue_item = InstallQueueItemModel(options=self.__options, download=None)
@@ -387,6 +373,3 @@ class TagCheckBox(QCheckBox):
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False
-
-
-
diff --git a/rare/components/dialogs/selective_dialog.py b/rare/components/dialogs/selective_dialog.py
index 864eb657..1c3b5031 100644
--- a/rare/components/dialogs/selective_dialog.py
+++ b/rare/components/dialogs/selective_dialog.py
@@ -1,18 +1,11 @@
-from typing import List, Union, Optional
-
from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtWidgets import (
- QLabel,
- QVBoxLayout,
- QCheckBox,
- QLayout, QGroupBox,
-)
-from legendary.utils.selective_dl import get_sdl_appname
+from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLayout, QGroupBox
from rare.models.game import RareGame
from rare.models.install import SelectiveDownloadsModel
-from rare.widgets.dialogs import ButtonDialog, dialog_title_game
from rare.utils.misc import icon
+from rare.widgets.dialogs import ButtonDialog, dialog_title_game
+from rare.widgets.selective_widget import SelectiveWidget
class SelectiveDialog(ButtonDialog):
@@ -25,20 +18,18 @@ class SelectiveDialog(ButtonDialog):
title_label = QLabel(f"
{dialog_title_game(header, rgame.app_title)}
", self)
- self.core = rgame.core
self.rgame = rgame
+ self.selective_widget = SelectiveWidget(rgame, rgame.igame.platform, self)
- selectable_group = QGroupBox(self.tr("Optional downloads"), self)
- self.selectable_layout = QVBoxLayout(selectable_group)
- self.selectable_layout.setSpacing(0)
-
- self.selectable_checks: List[TagCheckBox] = []
- self.config_tags: Optional[List[str]] = None
+ container = QGroupBox(self.tr("Optional downloads"), self)
+ container_layout = QVBoxLayout(container)
+ container_layout.setContentsMargins(0, 0, 0, 0)
+ container_layout.addWidget(self.selective_widget)
layout = QVBoxLayout()
layout.setSizeConstraint(QLayout.SetFixedSize)
layout.addWidget(title_label)
- layout.addWidget(selectable_group)
+ layout.addWidget(container)
self.setCentralLayout(layout)
@@ -47,62 +38,13 @@ class SelectiveDialog(ButtonDialog):
self.options: SelectiveDownloadsModel = SelectiveDownloadsModel(rgame.app_name)
- config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, "disable_sdl", fallback=False)
- sdl_name = get_sdl_appname(self.rgame.app_name)
- if not config_disable_sdl and sdl_name is not None:
- self.create_sdl_list()
- else:
- self.options.accepted = True
- self.accept()
-
- def create_sdl_list(self):
- platform = self.rgame.igame.platform
- for cb in self.selectable_checks:
- cb.disconnect()
- cb.deleteLater()
- self.selectable_checks.clear()
-
- if config_tags := self.core.lgd.config.get(self.rgame.app_name, "install_tags", fallback=None):
- self.config_tags = config_tags.split(",")
- config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, "disable_sdl", fallback=False)
- sdl_name = get_sdl_appname(self.rgame.app_name)
- if not config_disable_sdl and sdl_name is not None:
- sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
- if sdl_data:
- for tag, info in sdl_data.items():
- cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
- if tag == "__required":
- cb.setChecked(True)
- cb.setDisabled(True)
- if self.config_tags is not None:
- if all(elem in self.config_tags for elem in info["tags"]):
- cb.setChecked(True)
- self.selectable_layout.addWidget(cb)
- self.selectable_checks.append(cb)
-
def done_handler(self):
self.result_ready.emit(self.rgame, self.options)
def accept_handler(self):
- install_tag = [""]
- for cb in self.selectable_checks:
- if data := cb.isChecked():
- # noinspection PyTypeChecker
- install_tag.extend(data)
self.options.accepted = True
- self.options.install_tag = install_tag
+ self.options.install_tag = self.selective_widget.install_tags()
def reject_handler(self):
self.options.accepted = False
self.options.install_tag = None
-
-
-class TagCheckBox(QCheckBox):
- def __init__(self, text, desc, tags: List[str], parent=None):
- super(TagCheckBox, self).__init__(parent)
- self.setText(text)
- self.setToolTip(desc)
- self.tags = tags
-
- def isChecked(self) -> Union[bool, List[str]]:
- return self.tags if super(TagCheckBox, self).isChecked() else False
diff --git a/rare/ui/components/dialogs/install_dialog.py b/rare/ui/components/dialogs/install_dialog.py
index 10c5d871..8dc5c664 100644
--- a/rare/ui/components/dialogs/install_dialog.py
+++ b/rare/ui/components/dialogs/install_dialog.py
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_InstallDialog(object):
def setupUi(self, InstallDialog):
InstallDialog.setObjectName("InstallDialog")
- InstallDialog.resize(179, 204)
+ InstallDialog.resize(197, 216)
InstallDialog.setWindowTitle("InstallDialog")
self.main_layout = QtWidgets.QFormLayout(InstallDialog)
self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
diff --git a/rare/ui/components/dialogs/install_dialog.ui b/rare/ui/components/dialogs/install_dialog.ui
index e47e3eaf..d16cf4d4 100644
--- a/rare/ui/components/dialogs/install_dialog.ui
+++ b/rare/ui/components/dialogs/install_dialog.ui
@@ -6,8 +6,8 @@
0
0
- 179
- 204
+ 197
+ 216
diff --git a/rare/widgets/collapsible_widget.py b/rare/widgets/collapsible_widget.py
index 86ce6969..bd5d8ab6 100644
--- a/rare/widgets/collapsible_widget.py
+++ b/rare/widgets/collapsible_widget.py
@@ -101,9 +101,7 @@ class CollapsibleBase(object):
class CollapsibleFrame(QFrame, CollapsibleBase):
- def __init__(
- self, widget: QWidget = None, title: str = "", button_text: str = "", animation_duration: int = 200, parent=None
- ):
+ def __init__(self, animation_duration: int = 200, parent=None):
super(CollapsibleFrame, self).__init__(parent=parent)
self.setup(animation_duration)
self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
@@ -111,11 +109,10 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
self.toggle_button = QToolButton(self)
self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggle_button.setIcon(icon("fa.arrow-right"))
- self.toggle_button.setText(button_text)
self.toggle_button.setCheckable(True)
self.toggle_button.setChecked(False)
- self.title_label = QLabel(title)
+ self.title_label = QLabel(self)
font = self.title_label.font()
font.setBold(True)
self.title_label.setFont(font)
@@ -134,8 +131,11 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
self.toggle_button.clicked.connect(self.animationStart)
- if widget is not None:
- self.setWidget(widget)
+ def setTitle(self, title: str):
+ self.title_label.setText(title)
+
+ def setText(self, text: str):
+ self.toggle_button.setText(text)
def isChecked(self) -> bool:
return self.toggle_button.isChecked()
@@ -156,12 +156,9 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
class CollapsibleGroupBox(QGroupBox, CollapsibleBase):
- def __init__(
- self, widget: QWidget = None, title: str = "", animation_duration: int = 200, parent=None
- ):
+ def __init__(self, animation_duration: int = 200, parent=None):
super(CollapsibleGroupBox, self).__init__(parent=parent)
self.setup(animation_duration)
- self.setTitle(title)
self.setCheckable(True)
self.setChecked(False)
@@ -173,9 +170,6 @@ class CollapsibleGroupBox(QGroupBox, CollapsibleBase):
self.toggled.connect(self.animationStart)
- if widget is not None:
- self.setWidget(widget)
-
def isChecked(self) -> bool:
return super(CollapsibleGroupBox, self).isChecked()
@@ -202,7 +196,9 @@ if __name__ == "__main__":
ui_frame = Ui_InstallDialogAdvanced()
widget_frame = QWidget()
ui_frame.setupUi(widget_frame)
- collapsible_frame = CollapsibleFrame(widget_frame, title="Frame me!")
+ collapsible_frame = CollapsibleFrame()
+ collapsible_frame.setWidget(widget_frame)
+ collapsible_frame.setTitle("Frame me!")
collapsible_frame.setDisabled(False)
def replace_func_frame(state):
@@ -217,7 +213,9 @@ if __name__ == "__main__":
ui_group = Ui_InstallDialogAdvanced()
widget_group = QWidget()
ui_group.setupUi(widget_group)
- collapsible_group = CollapsibleGroupBox(widget_group, title="Group me!")
+ collapsible_group = CollapsibleGroupBox()
+ collapsible_group.setWidget(widget_group)
+ collapsible_group.setTitle("Group me!")
collapsible_group.setDisabled(False)
def replace_func_group(state):
diff --git a/rare/widgets/selective_widget.py b/rare/widgets/selective_widget.py
new file mode 100644
index 00000000..85cb1385
--- /dev/null
+++ b/rare/widgets/selective_widget.py
@@ -0,0 +1,55 @@
+from typing import List, Union
+
+from PyQt5.QtCore import Qt, pyqtSignal
+from PyQt5.QtWidgets import QCheckBox, QWidget, QVBoxLayout
+from legendary.utils.selective_dl import get_sdl_appname
+
+from rare.models.game import RareGame
+
+
+class TagCheckBox(QCheckBox):
+ def __init__(self, text, desc, tags: List[str], parent=None):
+ super(TagCheckBox, self).__init__(parent)
+ self.setText(text)
+ self.setToolTip(desc)
+ self.tags = tags
+
+ def isChecked(self) -> Union[bool, List[str]]:
+ return self.tags if super(TagCheckBox, self).isChecked() else False
+
+
+class SelectiveWidget(QWidget):
+ stateChanged: pyqtSignal = pyqtSignal()
+
+ def __init__(self, rgame: RareGame, platform: str, parent=None):
+ super().__init__(parent=parent)
+
+ main_layout = QVBoxLayout(self)
+ main_layout.setSpacing(0)
+
+ core = rgame.core
+
+ config_tags = core.lgd.config.get(rgame.app_name, "install_tags", fallback=None)
+ config_disable_sdl = core.lgd.config.getboolean(rgame.app_name, "disable_sdl", fallback=False)
+ sdl_name = get_sdl_appname(rgame.app_name)
+ if not config_disable_sdl and sdl_name is not None:
+ sdl_data = core.get_sdl_data(sdl_name, platform=platform)
+ if sdl_data:
+ for tag, info in sdl_data.items():
+ cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
+ if tag == "__required":
+ cb.setChecked(True)
+ cb.setDisabled(True)
+ if config_tags is not None:
+ if all(elem in config_tags for elem in info["tags"]):
+ cb.setChecked(True)
+ cb.stateChanged.connect(self.stateChanged)
+ main_layout.addWidget(cb)
+
+ def install_tags(self):
+ install_tags = [""]
+ for cb in self.findChildren(TagCheckBox, options=Qt.FindDirectChildrenOnly):
+ if data := cb.isChecked():
+ # noinspection PyTypeChecker
+ install_tags.extend(data)
+ return install_tags
diff --git a/requirements-flatpak.txt b/requirements-flatpak.txt
index 2f8872a6..893a7e2f 100644
--- a/requirements-flatpak.txt
+++ b/requirements-flatpak.txt
@@ -1,4 +1,4 @@
-requests
+requests<3.0
QtAwesome
setuptools
legendary-gl>=0.20.34
diff --git a/requirements-full.txt b/requirements-full.txt
index f8dbdeb8..55b8e26f 100644
--- a/requirements-full.txt
+++ b/requirements-full.txt
@@ -1,4 +1,4 @@
-requests
+requests<3.0
PyQt5
QtAwesome
setuptools
diff --git a/requirements.txt b/requirements.txt
index b8298181..6d4303ef 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-requests
+requests<3.0
PyQt5
QtAwesome
setuptools
From 6cbad745dfe6aa71cbadf70266ef08582f2eea8f Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sat, 13 Jan 2024 15:57:00 +0200
Subject: [PATCH 07/29] SelectiveWidget: Disabled parent if there aren't any
SDLs.
---
rare/widgets/selective_widget.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/rare/widgets/selective_widget.py b/rare/widgets/selective_widget.py
index 85cb1385..527697cc 100644
--- a/rare/widgets/selective_widget.py
+++ b/rare/widgets/selective_widget.py
@@ -45,6 +45,9 @@ class SelectiveWidget(QWidget):
cb.setChecked(True)
cb.stateChanged.connect(self.stateChanged)
main_layout.addWidget(cb)
+ self.parent().setDisabled(False)
+ else:
+ self.parent().setDisabled(True)
def install_tags(self):
install_tags = [""]
From 29bd7b81cb6daaa39afa6a96f0a33a7761b4f17b Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Thu, 18 Jan 2024 16:41:23 +0200
Subject: [PATCH 08/29] Launcher: Restore compatibility with legendary 0.22.34
---
rare/launcher/lgd_helper.py | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/rare/launcher/lgd_helper.py b/rare/launcher/lgd_helper.py
index 38c21254..4f6ab032 100644
--- a/rare/launcher/lgd_helper.py
+++ b/rare/launcher/lgd_helper.py
@@ -105,9 +105,15 @@ def get_game_params(rgame: RareGameSlim, args: InitArgs, launch_args: LaunchArgs
app_name = rgame.game.metadata['mainGameItem']['releaseInfo'][0]['appId']
rgame.igame = rgame.core.get_installed_game(app_name)
- params: LaunchParameters = rgame.core.get_launch_parameters(
- app_name=rgame.game.app_name, offline=args.offline, addon_app_name=rgame.igame.app_name
- )
+ try:
+ params: LaunchParameters = rgame.core.get_launch_parameters(
+ app_name=rgame.game.app_name, offline=args.offline, addon_app_name=rgame.igame.app_name
+ )
+ except TypeError:
+ logger.warning("Using older get_launch_parameters due to legendary version")
+ params: LaunchParameters = rgame.core.get_launch_parameters(
+ app_name=rgame.game.app_name, offline=args.offline
+ )
full_params = []
launch_args.environment = QProcessEnvironment.systemEnvironment()
From 2a0f80a9f028b13144afc1db69b65193ea1f3a58 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 8 Sep 2023 17:07:43 +0300
Subject: [PATCH 09/29] RareGame: RareEosOverlay now implements its procedures
internally.
The new methods are the following
* `has_update`: to check for updates
* `is_enabled`: checks if the overlay is enabled (on wine platform, on prefix)
* `enable`: enables the overlay (on wine platforms, on prefix, for app_name)
* `disable`: disables the overlay (on wine platforms, on prefix, for app_name)
* `install`: Install using Rare's existing facilities
* `uninstall`: Uninstall using Rare's existing facilities
---
rare/models/game.py | 63 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 62 insertions(+), 1 deletion(-)
diff --git a/rare/models/game.py b/rare/models/game.py
index ea701015..f45b8709 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -9,12 +9,13 @@ from typing import List, Optional, Dict, Set
from PyQt5.QtCore import QRunnable, pyqtSlot, QProcess, QThreadPool
from PyQt5.QtGui import QPixmap
+from legendary.lfs import eos
from legendary.models.game import Game, InstalledGame
from legendary.utils.selective_dl import get_sdl_appname
from rare.lgndr.core import LegendaryCore
-from rare.models.install import InstallOptionsModel, UninstallOptionsModel
from rare.models.base_game import RareGameBase, RareGameSlim
+from rare.models.install import InstallOptionsModel, UninstallOptionsModel
from rare.shared.game_process import GameProcess
from rare.shared.image_manager import ImageManager
from rare.utils.paths import data_dir, get_rare_executable
@@ -572,3 +573,63 @@ class RareEosOverlay(RareGameBase):
else:
self.igame = None
self.signals.game.uninstalled.emit(self.app_name)
+
+ @property
+ def has_update(self) -> bool:
+ self.core.check_for_overlay_updates()
+ return self.core.overlay_update_available
+
+ def is_enabled(self, prefix: Optional[str] = None):
+ reg_paths = eos.query_registry_entries(prefix)
+ return reg_paths["overlay_path"] and self.core.is_overlay_install(reg_paths["overlay_path"])
+
+ def enable(
+ self, prefix: Optional[str] = None, app_name: Optional[str] = None, path: Optional[str] = None
+ ) -> bool:
+ if not self.is_installed or self.is_enabled(prefix):
+ return False
+ if not path:
+ path = self.igame.install_path
+ reg_paths = eos.query_registry_entries(prefix)
+ if old_path := reg_paths["overlay_path"]:
+ if os.path.normpath(old_path) == path:
+ logger.info(f"Overlay already enabled, nothing to do.")
+ return True
+ else:
+ logger.info(f'Updating overlay registry entries from "{old_path}" to "{path}"')
+ eos.remove_registry_entries(prefix)
+ eos.add_registry_entries(path, prefix)
+ logger.info(f"Enabled overlay at: {path} for prefix: {prefix}")
+ return True
+
+ def disable(self, prefix: Optional[str] = None, app_name: Optional[str] = None) -> bool:
+ if not self.is_enabled(prefix):
+ return False
+ logger.info(f"Disabling overlay (removing registry keys) for prefix: {prefix}")
+ eos.remove_registry_entries(prefix)
+ return True
+
+ def install(self) -> bool:
+ if not self.is_idle:
+ return False
+ if self.is_installed:
+ base_path = self.igame.install_path
+ else:
+ base_path = os.path.join(
+ self.core.lgd.config.get("Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")),
+ ".overlay"
+ )
+ self.signals.game.install.emit(
+ InstallOptionsModel(
+ app_name=self.app_name, base_path=base_path, platform="Windows", overlay=True
+ )
+ )
+ return True
+
+ def uninstall(self) -> bool:
+ if not self.is_idle or not self.is_installed:
+ return False
+ self.signals.game.uninstall.emit(
+ UninstallOptionsModel(app_name=self.app_name)
+ )
+ return True
From a246f4ee1650906f29f9fb7540e7afed30ddb09a Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 8 Sep 2023 19:38:39 +0300
Subject: [PATCH 10/29] EosGroup: Remake the UI and integrate with Rare's
facilities
* Uninstalling the Overlay now goes through the same procedure as
uninstalling any other game.
* The available prefixes are now listed instead of hiding them inside
a combo box.
* Each listing works indepedently to enable/disable the Overlay for the prefix
---
rare/components/dialogs/uninstall_dialog.py | 10 +-
rare/components/tabs/downloads/__init__.py | 5 +-
.../tabs/games/integrations/__init__.py | 4 +-
.../tabs/games/integrations/eos_group.py | 296 +++++++++---------
rare/models/install.py | 9 +-
rare/models/signals.py | 6 +-
rare/shared/rare_core.py | 10 +-
rare/shared/workers/uninstall.py | 43 ++-
.../tabs/games/integrations/eos_widget.py | 160 ++++------
.../tabs/games/integrations/eos_widget.ui | 287 +++++++----------
10 files changed, 375 insertions(+), 455 deletions(-)
diff --git a/rare/components/dialogs/uninstall_dialog.py b/rare/components/dialogs/uninstall_dialog.py
index c6f24cae..dd6be379 100644
--- a/rare/components/dialogs/uninstall_dialog.py
+++ b/rare/components/dialogs/uninstall_dialog.py
@@ -23,14 +23,21 @@ class UninstallDialog(ButtonDialog):
self.keep_files = QCheckBox(self.tr("Keep files"))
self.keep_files.setChecked(bool(options.keep_files))
+ self.keep_files.setEnabled(not rgame.is_overlay)
self.keep_config = QCheckBox(self.tr("Keep configuation"))
self.keep_config.setChecked(bool(options.keep_config))
+ self.keep_config.setEnabled(not rgame.is_overlay)
+
+ self.keep_overlay_keys = QCheckBox(self.tr("Keep EOS Overlay registry keys"))
+ self.keep_overlay_keys.setChecked(bool(options.keep_overlay_keys))
+ self.keep_overlay_keys.setEnabled(rgame.is_overlay)
layout = QVBoxLayout()
layout.addWidget(title_label)
layout.addWidget(self.keep_files)
layout.addWidget(self.keep_config)
+ layout.addWidget(self.keep_overlay_keys)
self.setCentralLayout(layout)
@@ -51,7 +58,8 @@ class UninstallDialog(ButtonDialog):
True,
self.keep_files.isChecked(),
self.keep_config.isChecked(),
+ self.keep_overlay_keys.isChecked(),
)
def reject_handler(self):
- self.options.values = (None, None, None)
+ self.options.values = (None, None, None, None)
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index 9166f724..85df8598 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -238,10 +238,7 @@ class DownloadsTab(QWidget):
else:
logger.info(f"Created desktop link {result.shortcut_name} for {result.options.app_name}")
- if result.options.overlay:
- self.signals.application.overlay_installed.emit()
- else:
- self.signals.application.notify.emit(result.options.app_name)
+ self.signals.application.notify.emit(result.options.app_name)
if self.updates_group.contains(result.options.app_name):
self.updates_group.set_widget_enabled(result.options.app_name, True)
diff --git a/rare/components/tabs/games/integrations/__init__.py b/rare/components/tabs/games/integrations/__init__.py
index 0a3bf0fb..0ecbc088 100644
--- a/rare/components/tabs/games/integrations/__init__.py
+++ b/rare/components/tabs/games/integrations/__init__.py
@@ -5,7 +5,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLabel, QSpacerItem, QSizePoli
from rare.widgets.side_tab import SideTabWidget
from .egl_sync_group import EGLSyncGroup
-from .eos_group import EOSGroup
+from .eos_group import EosGroup
from .import_group import ImportGroup
from .ubisoft_group import UbisoftGroup
@@ -35,7 +35,7 @@ class IntegrationsTabs(SideTabWidget):
self,
)
self.ubisoft_group = UbisoftGroup(self.eos_ubisoft)
- self.eos_group = EOSGroup(self.eos_ubisoft)
+ self.eos_group = EosGroup(self.eos_ubisoft)
self.eos_ubisoft.addWidget(self.eos_group)
self.eos_ubisoft.addWidget(self.ubisoft_group)
self.eos_ubisoft_index = self.addTab(self.eos_ubisoft, self.tr("Epic Overlay and Ubisoft"))
diff --git a/rare/components/tabs/games/integrations/eos_group.py b/rare/components/tabs/games/integrations/eos_group.py
index 9e541d25..154c820f 100644
--- a/rare/components/tabs/games/integrations/eos_group.py
+++ b/rare/components/tabs/games/integrations/eos_group.py
@@ -1,161 +1,189 @@
import os
import platform
from logging import getLogger
-from typing import List
+from typing import Optional
-from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool
-from PyQt5.QtWidgets import QGroupBox, QMessageBox
+from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, Qt, pyqtSlot, QSize
+from PyQt5.QtWidgets import QGroupBox, QMessageBox, QFrame, QHBoxLayout, QSizePolicy, QLabel, QPushButton, QFormLayout
from legendary.lfs import eos
-from rare.models.install import InstallOptionsModel
-from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
+from rare.lgndr.core import LegendaryCore
+from rare.models.game import RareEosOverlay
+from rare.shared import RareCore
from rare.ui.components.tabs.games.integrations.eos_widget import Ui_EosWidget
+from rare.utils import config_helper
from rare.utils.misc import icon
+from rare.widgets.elide_label import ElideLabel
logger = getLogger("EpicOverlay")
-def get_wine_prefixes() -> List[str]:
- prefixes = list()
- if os.path.exists(p := os.path.expanduser("~/.wine")):
- prefixes.append(p)
-
- for name, section in LegendaryCoreSingleton().lgd.config.items():
- pfx = section.get("WINEPREFIX") or section.get("wine_prefix")
- if pfx and pfx not in prefixes:
- prefixes.append(pfx)
-
- return prefixes
-
-
class CheckForUpdateWorker(QRunnable):
class CheckForUpdateSignals(QObject):
update_available = pyqtSignal(bool)
- def __init__(self):
+ def __init__(self, core: LegendaryCore):
super(CheckForUpdateWorker, self).__init__()
self.signals = self.CheckForUpdateSignals()
self.setAutoDelete(True)
- self.core = LegendaryCoreSingleton()
+ self.core = core
def run(self) -> None:
self.core.check_for_overlay_updates()
self.signals.update_available.emit(self.core.overlay_update_available)
-class EOSGroup(QGroupBox):
+class EosPrefixWidget(QFrame):
+ def __init__(self, overlay: RareEosOverlay, prefix: Optional[str], parent=None):
+ super(EosPrefixWidget, self).__init__(parent=parent)
+ self.setFrameShape(QFrame.StyledPanel)
+ self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+
+ self.indicator = QLabel(parent=self)
+ self.indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
+
+ self.label = ElideLabel(
+ prefix if prefix is not None else "Epic Online Services Overlay",
+ parent=self
+ )
+
+ self.button = QPushButton(parent=self)
+ self.button.setMinimumWidth(150)
+ self.button.clicked.connect(self.action)
+
+ layout = QHBoxLayout(self)
+ layout.setContentsMargins(-1, 0, 0, 0)
+ layout.addWidget(self.indicator)
+ layout.addWidget(self.label, stretch=1)
+ layout.addWidget(self.button)
+
+ self.overlay = overlay
+ self.prefix = prefix
+
+ self.overlay.signals.game.installed.connect(self.update_state)
+ self.overlay.signals.game.uninstalled.connect(self.update_state)
+
+ self.update_state()
+
+ @pyqtSlot()
+ def update_state(self):
+ if not self.overlay.is_installed:
+ self.setDisabled(True)
+ self.button.setText(self.tr("Unavailable"))
+ self.indicator.setPixmap(icon("fa.circle-o", color="grey").pixmap(20, 20))
+ return
+
+ self.setDisabled(False)
+ if self.overlay.is_enabled(self.prefix):
+ self.button.setText(self.tr("Disable overlay"))
+ self.indicator.setPixmap(
+ icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20))
+ )
+ else:
+ self.button.setText(self.tr("Enable overlay"))
+ self.indicator.setPixmap(
+ icon("fa.times-circle-o", color="red").pixmap(QSize(20, 20))
+ )
+
+ @pyqtSlot()
+ def action(self):
+ if self.overlay.is_enabled(self.prefix):
+ self.overlay.disable(prefix=self.prefix)
+ else:
+ self.overlay.enable(prefix=self.prefix)
+ self.update_state()
+
+
+class EosGroup(QGroupBox):
def __init__(self, parent=None):
- super(EOSGroup, self).__init__(parent=parent)
+ super(EosGroup, self).__init__(parent=parent)
self.ui = Ui_EosWidget()
self.ui.setupUi(self)
# lk: set object names for CSS properties
self.ui.install_button.setObjectName("InstallButton")
- self.ui.install_button.setIcon(icon("ri.install-line"))
self.ui.uninstall_button.setObjectName("UninstallButton")
+
+ self.ui.install_page_layout.setAlignment(Qt.AlignTop)
+ self.ui.info_page_layout.setAlignment(Qt.AlignTop)
+
+ self.ui.install_button.setIcon(icon("ri.install-line"))
self.ui.uninstall_button.setIcon(icon("ri.uninstall-line"))
- self.core = LegendaryCoreSingleton()
- self.signals = GlobalSignalsSingleton()
+ self.installed_path_label = ElideLabel(parent=self)
+ self.installed_version_label = ElideLabel(parent=self)
- self.prefix_enabled = False
+ self.ui.info_label_layout.setWidget(0, QFormLayout.FieldRole, self.installed_version_label)
+ self.ui.info_label_layout.setWidget(1, QFormLayout.FieldRole, self.installed_path_label)
- self.ui.enabled_cb.stateChanged.connect(self.change_enable)
+ self.rcore = RareCore.instance()
+ self.core = self.rcore.core()
+ self.signals = self.rcore.signals()
+ self.overlay = self.rcore.get_overlay()
+
+ self.overlay.signals.game.installed.connect(self.install_finished)
+ self.overlay.signals.game.uninstalled.connect(self.uninstall_finished)
+
+ self.ui.install_button.clicked.connect(self.install_overlay)
+ self.ui.update_button.clicked.connect(self.install_overlay)
self.ui.uninstall_button.clicked.connect(self.uninstall_overlay)
- self.ui.update_button.setVisible(False)
- self.overlay = self.core.lgd.get_overlay_install_info()
-
- self.signals.application.overlay_installed.connect(self.overlay_installation_finished)
- self.signals.application.prefix_updated.connect(self.update_prefixes)
-
- self.ui.update_check_button.clicked.connect(self.check_for_update)
- self.ui.install_button.clicked.connect(self.install_overlay)
- self.ui.update_button.clicked.connect(lambda: self.install_overlay(True))
-
- if self.overlay: # installed
- self.ui.installed_version_lbl.setText(f"{self.overlay.version}")
- self.ui.installed_path_lbl.setText(f"{self.overlay.install_path}")
- self.ui.overlay_stack.setCurrentIndex(0)
+ if self.overlay.is_installed: # installed
+ self.installed_version_label.setText(f"{self.overlay.version}")
+ self.installed_path_label.setText(self.overlay.install_path)
+ self.ui.overlay_stack.setCurrentWidget(self.ui.info_page)
else:
- self.ui.overlay_stack.setCurrentIndex(1)
- self.ui.enable_frame.setDisabled(True)
-
- if platform.system() == "Windows":
- self.current_prefix = None
- self.ui.select_pfx_combo.setVisible(False)
- else:
- self.current_prefix = os.path.expanduser("~/.wine") \
- if os.path.exists(os.path.expanduser("~/.wine")) \
- else None
- pfxs = get_wine_prefixes()
- for pfx in pfxs:
- self.ui.select_pfx_combo.addItem(pfx.replace(os.path.expanduser("~/"), "~/"))
- if not pfxs:
- self.ui.enable_frame.setDisabled(True)
- else:
- self.ui.select_pfx_combo.setCurrentIndex(0)
-
- self.ui.select_pfx_combo.currentIndexChanged.connect(self.update_select_combo)
- if pfxs:
- self.update_select_combo(None)
-
- self.ui.enabled_info_label.setText("")
+ self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
+ self.ui.update_button.setEnabled(False)
self.threadpool = QThreadPool.globalInstance()
+ def showEvent(self, a0) -> None:
+ self.check_for_update()
+ self.update_prefixes()
+ super().showEvent(a0)
+
def update_prefixes(self):
- logger.debug("Updated prefixes")
- pfxs = get_wine_prefixes() # returns /home/whatever
- self.ui.select_pfx_combo.clear()
+ for widget in self.findChildren(EosPrefixWidget, options=Qt.FindDirectChildrenOnly):
+ widget.deleteLater()
- for pfx in pfxs:
- self.ui.select_pfx_combo.addItem(pfx.replace(os.path.expanduser("~/"), "~/"))
-
- if self.current_prefix in pfxs:
- self.ui.select_pfx_combo.setCurrentIndex(
- self.ui.select_pfx_combo.findText(self.current_prefix.replace(os.path.expanduser("~/"), "~/")))
+ if platform.system() != "Windows":
+ prefixes = config_helper.get_wine_prefixes()
+ if platform.system() == "Darwin":
+ # TODO: add crossover support
+ pass
+ for prefix in prefixes:
+ widget = EosPrefixWidget(self.overlay, prefix)
+ self.ui.eos_layout.addWidget(widget)
+ logger.debug("Updated prefixes")
+ else:
+ widget = EosPrefixWidget(self.overlay, None)
+ self.ui.eos_layout.addWidget(widget)
def check_for_update(self):
- def worker_finished(update_available):
- self.ui.update_button.setVisible(update_available)
- self.ui.update_check_button.setDisabled(False)
- if not update_available:
- self.ui.update_check_button.setText(self.tr("No update available"))
+ if not self.overlay.is_installed:
+ return
- self.ui.update_check_button.setDisabled(True)
- worker = CheckForUpdateWorker()
+ def worker_finished(update_available):
+ self.ui.update_button.setEnabled(update_available)
+
+ worker = CheckForUpdateWorker(self.core)
worker.signals.update_available.connect(worker_finished)
QThreadPool.globalInstance().start(worker)
- def overlay_installation_finished(self):
- self.overlay = self.core.lgd.get_overlay_install_info()
-
- if not self.overlay:
- logger.error("Something went wrong, when installing overlay")
- QMessageBox.warning(self, "Error", self.tr("Something went wrong, when installing overlay"))
+ @pyqtSlot()
+ def install_finished(self):
+ if not self.overlay.is_installed:
+ logger.error("Something went wrong while installing overlay")
+ QMessageBox.warning(self, "Error", self.tr("Something went wrong while installing Overlay"))
return
+ self.ui.overlay_stack.setCurrentWidget(self.ui.info_page)
+ self.installed_version_label.setText(f"{self.overlay.version}")
+ self.installed_path_label.setText(self.overlay.install_path)
+ self.ui.update_button.setEnabled(False)
- self.ui.overlay_stack.setCurrentIndex(0)
- self.ui.installed_version_lbl.setText(f"{self.overlay.version}")
- self.ui.installed_path_lbl.setText(f"{self.overlay.install_path}")
-
- self.ui.update_button.setVisible(False)
-
- self.ui.enable_frame.setEnabled(True)
-
- def update_select_combo(self, i: None):
- if i is None:
- i = self.ui.select_pfx_combo.currentIndex()
- prefix = os.path.expanduser(self.ui.select_pfx_combo.itemText(i))
- if platform.system() != "Windows" and not os.path.isfile(os.path.join(prefix, "user.reg")):
- return
- self.current_prefix = prefix
- reg_paths = eos.query_registry_entries(self.current_prefix)
-
- overlay_enabled = False
- if reg_paths['overlay_path'] and self.core.is_overlay_install(reg_paths['overlay_path']):
- overlay_enabled = True
- self.ui.enabled_cb.setChecked(overlay_enabled)
+ @pyqtSlot()
+ def uninstall_finished(self):
+ self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
def change_enable(self):
enabled = self.ui.enabled_cb.isChecked()
@@ -170,7 +198,7 @@ class EOSGroup(QGroupBox):
logger.info("Disabled Epic Overlay")
self.ui.enabled_info_label.setText(self.tr("Disabled"))
else:
- if not self.overlay:
+ if not self.overlay.is_installed:
available_installs = self.core.search_overlay_installs(self.current_prefix)
if not available_installs:
logger.error('No EOS overlay installs found!')
@@ -209,51 +237,13 @@ class EOSGroup(QGroupBox):
self.ui.enabled_info_label.setText(self.tr("Enabled"))
logger.info(f'Enabled overlay at: {path}')
- def update_checkbox(self):
- reg_paths = eos.query_registry_entries(self.current_prefix)
- enabled = False
- if reg_paths['overlay_path'] and self.core.is_overlay_install(reg_paths['overlay_path']):
- enabled = True
- self.ui.enabled_cb.setChecked(enabled)
-
- def install_overlay(self, update=False):
- base_path = os.path.join(self.core.get_default_install_dir(), ".overlay")
- if update:
- if not self.overlay:
- self.ui.overlay_stack.setCurrentIndex(1)
- self.ui.enable_frame.setDisabled(True)
- QMessageBox.warning(self, "Warning", self.tr("Overlay is not installed. Could not update"))
- return
- base_path = self.overlay.install_path
-
- options = InstallOptionsModel(
- app_name=eos.EOSOverlayApp.app_name, base_path=base_path, platform="Windows", overlay=True
- )
-
- self.signals.game.install.emit(options)
+ @pyqtSlot()
+ def install_overlay(self):
+ self.overlay.install()
def uninstall_overlay(self):
- if not self.core.is_overlay_installed():
- logger.error('No legendary-managed overlay installation found.')
- self.ui.overlay_stack.setCurrentIndex(1)
+ if not self.overlay.is_installed:
+ logger.error('No Rare-managed overlay installation found.')
+ self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
return
-
- if QMessageBox.No == QMessageBox.question(
- self, "Uninstall Overlay", self.tr("Do you want to uninstall overlay?"),
- QMessageBox.Yes | QMessageBox.No, QMessageBox.No
- ):
- return
- if platform.system() == "Windows":
- eos.remove_registry_entries(None)
- else:
- for prefix in [self.ui.select_pfx_combo.itemText(i) for i in range(self.ui.select_pfx_combo.count())]:
- logger.info(f"Removing registry entries from {prefix}")
- try:
- eos.remove_registry_entries(os.path.expanduser(prefix))
- except Exception as e:
- logger.warning(f"{prefix}: {e}")
-
- self.core.remove_overlay_install()
- self.ui.overlay_stack.setCurrentIndex(1)
-
- self.ui.enable_frame.setDisabled(True)
+ self.overlay.uninstall()
diff --git a/rare/models/install.py b/rare/models/install.py
index 69123d0b..0a4965d6 100644
--- a/rare/models/install.py
+++ b/rare/models/install.py
@@ -86,6 +86,7 @@ class UninstallOptionsModel:
accepted: bool = None
keep_files: bool = None
keep_config: bool = None
+ keep_overlay_keys: bool = None
def __bool__(self):
return (
@@ -93,20 +94,21 @@ class UninstallOptionsModel:
and (self.accepted is not None)
and (self.keep_files is not None)
and (self.keep_config is not None)
+ and (self.keep_overlay_keys is not None)
)
@property
- def values(self) -> Tuple[bool, bool, bool]:
+ def values(self) -> Tuple[bool, bool, bool, bool]:
"""
This model's options
:return:
Tuple of `accepted` `keep_files` `keep_config` `keep_overlay_keys`
"""
- return self.accepted, self.keep_config, self.keep_files
+ return self.accepted, self.keep_config, self.keep_files, self.keep_overlay_keys
@values.setter
- def values(self, values: Tuple[bool, bool, bool]):
+ def values(self, values: Tuple[bool, bool, bool, bool]):
"""
Set this model's options
@@ -117,6 +119,7 @@ class UninstallOptionsModel:
self.accepted = values[0]
self.keep_files = values[1]
self.keep_config = values[2]
+ self.keep_overlay_keys = values[3]
@dataclass
diff --git a/rare/models/signals.py b/rare/models/signals.py
index 8377e2d3..3c614237 100644
--- a/rare/models/signals.py
+++ b/rare/models/signals.py
@@ -10,14 +10,12 @@ class GlobalSignals:
class ApplicationSignals(QObject):
# int: exit code
- quit = pyqtSignal(int)
+ quit = pyqtSignal(int)
# str: app_title
notify = pyqtSignal(str)
# none
prefix_updated = pyqtSignal()
# none
- overlay_installed = pyqtSignal()
- # none
update_tray = pyqtSignal()
# none
update_statusbar = pyqtSignal()
@@ -58,4 +56,4 @@ class GlobalSignals:
self.download.deleteLater()
del self.download
self.discord_rpc.deleteLater()
- del self.discord_rpc
\ No newline at end of file
+ del self.discord_rpc
diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py
index 6000a3b9..31a80f06 100644
--- a/rare/shared/rare_core.py
+++ b/rare/shared/rare_core.py
@@ -66,6 +66,8 @@ class RareCore(QObject):
self.__library: Dict[str, RareGame] = {}
self.__eos_overlay = RareEosOverlay(self.__core, EOSOverlayApp)
+ self.__eos_overlay.signals.game.install.connect(self.__signals.game.install)
+ self.__eos_overlay.signals.game.uninstall.connect(self.__signals.game.uninstall)
RareCore.__instance = self
@@ -224,12 +226,12 @@ class RareCore(QObject):
for dlc in rgame.owned_dlcs:
if dlc.is_installed:
logger.info(f'Uninstalling DLC "{dlc.app_name}" ({dlc.app_title})...')
- uninstall_game(self.__core, dlc.app_name, keep_files=True, keep_config=True)
+ uninstall_game(self.__core, dlc, keep_files=True, keep_config=True)
dlc.igame = None
logger.info(
f'Removing "{rgame.app_title}" because "{rgame.igame.install_path}" does not exist...'
)
- uninstall_game(self.__core, rgame.app_name, keep_files=True, keep_config=True)
+ uninstall_game(self.__core, rgame, keep_files=True, keep_config=True)
logger.info(f"Uninstalled {rgame.app_title}, because no game files exist")
rgame.igame = None
return
@@ -254,6 +256,9 @@ class RareCore(QObject):
return self.__eos_overlay
return self.__library[app_name]
+ def get_overlay(self):
+ return self.get_game(EOSOverlayApp.app_name)
+
def __add_game(self, rgame: RareGame) -> None:
rgame.signals.download.enqueue.connect(self.__signals.download.enqueue)
rgame.signals.download.dequeue.connect(self.__signals.download.dequeue)
@@ -345,7 +350,6 @@ class RareCore(QObject):
start_time = time.time()
try:
entitlements = self.__core.egs.get_user_entitlements()
- self.__core.lgd.entitlements = entitlements
for game in self.__library.values():
game.grant_date()
except (HTTPError, ConnectionError) as e:
diff --git a/rare/shared/workers/uninstall.py b/rare/shared/workers/uninstall.py
index 76207fe7..9b154ce8 100644
--- a/rare/shared/workers/uninstall.py
+++ b/rare/shared/workers/uninstall.py
@@ -1,7 +1,10 @@
+import platform
from logging import getLogger
+from typing import Tuple
from PyQt5.QtCore import QObject, pyqtSignal
from legendary.core import LegendaryCore
+from legendary.lfs.eos import remove_registry_entries
from rare.lgndr.cli import LegendaryCLI
from rare.lgndr.glue.arguments import LgndrUninstallGameArgs
@@ -16,14 +19,36 @@ logger = getLogger("UninstallWorker")
# TODO: You can use RareGame directly here once this is called inside RareCore and skip metadata fetch
-def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_config=False):
- game = core.get_game(app_name)
+def uninstall_game(
+ core: LegendaryCore, rgame: RareGame, keep_files=False, keep_config=False, keep_overlay_keys=False
+) -> Tuple[bool, str]:
+ if rgame.is_overlay:
+ logger.info('Deleting overlay installation...')
+ core.remove_overlay_install()
+
+ if keep_overlay_keys:
+ return True, ""
+
+ logger.info('Removing registry entries...')
+ if platform.system() != "Window":
+ prefixes = config_helper.get_wine_prefixes()
+ if platform.system() == "Darwin":
+ # TODO: add crossover support
+ pass
+ if prefixes is not None:
+ for prefix in prefixes:
+ remove_registry_entries(prefix)
+ logger.debug("Removed registry entries for prefix %s", prefix)
+ else:
+ remove_registry_entries(None)
+
+ return True, ""
# remove shortcuts link
if desktop_links_supported():
for link_type in desktop_link_types():
link_path = desktop_link_path(
- game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value"), link_type
+ rgame.game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value"), link_type
)
if link_path.exists():
link_path.unlink(missing_ok=True)
@@ -31,7 +56,7 @@ def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_co
status = LgndrIndirectStatus()
LegendaryCLI(core).uninstall_game(
LgndrUninstallGameArgs(
- app_name=app_name,
+ app_name=rgame.app_name,
keep_files=keep_files,
skip_uninstaller=False,
yes=True,
@@ -40,8 +65,8 @@ def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_co
)
if not keep_config:
logger.info("Removing sections in config file")
- config_helper.remove_section(app_name)
- config_helper.remove_section(f"{app_name}.env")
+ config_helper.remove_section(rgame.app_name)
+ config_helper.remove_section(f"{rgame.app_name}.env")
config_helper.save_config()
@@ -62,7 +87,11 @@ class UninstallWorker(Worker):
def run_real(self) -> None:
self.rgame.state = RareGame.State.UNINSTALLING
success, message = uninstall_game(
- self.core, self.rgame.app_name, self.options.keep_files, self.options.keep_config
+ self.core,
+ self.rgame,
+ self.options.keep_files,
+ self.options.keep_config,
+ self.options.keep_overlay_keys,
)
self.rgame.state = RareGame.State.IDLE
self.signals.result.emit(self.rgame, success, message)
diff --git a/rare/ui/components/tabs/games/integrations/eos_widget.py b/rare/ui/components/tabs/games/integrations/eos_widget.py
index 8b856256..3efcf3f9 100644
--- a/rare/ui/components/tabs/games/integrations/eos_widget.py
+++ b/rare/ui/components/tabs/games/integrations/eos_widget.py
@@ -14,102 +14,71 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_EosWidget(object):
def setupUi(self, EosWidget):
EosWidget.setObjectName("EosWidget")
- EosWidget.resize(586, 146)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(EosWidget.sizePolicy().hasHeightForWidth())
- EosWidget.setSizePolicy(sizePolicy)
+ EosWidget.resize(464, 98)
EosWidget.setWindowTitle("GroupBox")
- self.eos_layout = QtWidgets.QHBoxLayout(EosWidget)
+ self.eos_layout = QtWidgets.QVBoxLayout(EosWidget)
self.eos_layout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
self.eos_layout.setObjectName("eos_layout")
self.overlay_stack = QtWidgets.QStackedWidget(EosWidget)
- self.overlay_stack.setFrameShape(QtWidgets.QFrame.StyledPanel)
- self.overlay_stack.setFrameShadow(QtWidgets.QFrame.Raised)
self.overlay_stack.setObjectName("overlay_stack")
- self.overlay_info_page = QtWidgets.QWidget()
- self.overlay_info_page.setObjectName("overlay_info_page")
- self.overlay_info_layout = QtWidgets.QFormLayout(self.overlay_info_page)
- self.overlay_info_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.overlay_info_layout.setFormAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
- self.overlay_info_layout.setObjectName("overlay_info_layout")
- self.installed_version_info_lbl = QtWidgets.QLabel(self.overlay_info_page)
- self.installed_version_info_lbl.setObjectName("installed_version_info_lbl")
- self.overlay_info_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.installed_version_info_lbl)
- self.installed_version_lbl = QtWidgets.QLabel(self.overlay_info_page)
- self.installed_version_lbl.setText("error")
- self.installed_version_lbl.setObjectName("installed_version_lbl")
- self.overlay_info_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.installed_version_lbl)
- self.installed_path_info_lbl = QtWidgets.QLabel(self.overlay_info_page)
- self.installed_path_info_lbl.setObjectName("installed_path_info_lbl")
- self.overlay_info_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.installed_path_info_lbl)
- self.installed_path_lbl = QtWidgets.QLabel(self.overlay_info_page)
- self.installed_path_lbl.setText("error")
- self.installed_path_lbl.setObjectName("installed_path_lbl")
- self.overlay_info_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.installed_path_lbl)
- self.info_buttons_layout = QtWidgets.QHBoxLayout()
- self.info_buttons_layout.setObjectName("info_buttons_layout")
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.info_buttons_layout.addItem(spacerItem)
- self.uninstall_button = QtWidgets.QPushButton(self.overlay_info_page)
- self.uninstall_button.setMinimumSize(QtCore.QSize(120, 0))
- self.uninstall_button.setObjectName("uninstall_button")
- self.info_buttons_layout.addWidget(self.uninstall_button)
- self.update_check_button = QtWidgets.QPushButton(self.overlay_info_page)
- self.update_check_button.setMinimumSize(QtCore.QSize(120, 0))
- self.update_check_button.setObjectName("update_check_button")
- self.info_buttons_layout.addWidget(self.update_check_button)
- self.update_button = QtWidgets.QPushButton(self.overlay_info_page)
- self.update_button.setMinimumSize(QtCore.QSize(120, 0))
- self.update_button.setObjectName("update_button")
- self.info_buttons_layout.addWidget(self.update_button)
- self.overlay_info_layout.setLayout(3, QtWidgets.QFormLayout.SpanningRole, self.info_buttons_layout)
- spacerItem1 = QtWidgets.QSpacerItem(6, 6, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.overlay_info_layout.setItem(2, QtWidgets.QFormLayout.SpanningRole, spacerItem1)
- self.overlay_stack.addWidget(self.overlay_info_page)
- self.overlay_install_page = QtWidgets.QWidget()
- self.overlay_install_page.setObjectName("overlay_install_page")
- self.overlay_install_layout = QtWidgets.QFormLayout(self.overlay_install_page)
- self.overlay_install_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.overlay_install_layout.setFormAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
- self.overlay_install_layout.setObjectName("overlay_install_layout")
- self.label = QtWidgets.QLabel(self.overlay_install_page)
- self.label.setObjectName("label")
- self.overlay_install_layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.label)
- self.install_buttons_layout = QtWidgets.QHBoxLayout()
- self.install_buttons_layout.setObjectName("install_buttons_layout")
- spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.install_buttons_layout.addItem(spacerItem2)
- self.install_button = QtWidgets.QPushButton(self.overlay_install_page)
- self.install_button.setMinimumSize(QtCore.QSize(120, 0))
+ self.install_page = QtWidgets.QWidget()
+ self.install_page.setObjectName("install_page")
+ self.install_page_layout = QtWidgets.QHBoxLayout(self.install_page)
+ self.install_page_layout.setContentsMargins(0, 0, 0, 0)
+ self.install_page_layout.setObjectName("install_page_layout")
+ self.install_label_layout = QtWidgets.QFormLayout()
+ self.install_label_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.install_label_layout.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
+ self.install_label_layout.setObjectName("install_label_layout")
+ self.install_label = QtWidgets.QLabel(self.install_page)
+ self.install_label.setObjectName("install_label")
+ self.install_label_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.install_label)
+ self.install_text = QtWidgets.QLabel(self.install_page)
+ self.install_text.setObjectName("install_text")
+ self.install_label_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.install_text)
+ self.install_page_layout.addLayout(self.install_label_layout)
+ self.install_button_layout = QtWidgets.QVBoxLayout()
+ self.install_button_layout.setContentsMargins(-1, -1, 0, -1)
+ self.install_button_layout.setObjectName("install_button_layout")
+ self.install_button = QtWidgets.QPushButton(self.install_page)
+ self.install_button.setMinimumSize(QtCore.QSize(140, 0))
self.install_button.setObjectName("install_button")
- self.install_buttons_layout.addWidget(self.install_button)
- self.overlay_install_layout.setLayout(2, QtWidgets.QFormLayout.SpanningRole, self.install_buttons_layout)
- spacerItem3 = QtWidgets.QSpacerItem(6, 6, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.overlay_install_layout.setItem(1, QtWidgets.QFormLayout.SpanningRole, spacerItem3)
- self.overlay_stack.addWidget(self.overlay_install_page)
+ self.install_button_layout.addWidget(self.install_button)
+ self.install_page_layout.addLayout(self.install_button_layout)
+ self.install_page_layout.setStretch(0, 1)
+ self.overlay_stack.addWidget(self.install_page)
+ self.info_page = QtWidgets.QWidget()
+ self.info_page.setObjectName("info_page")
+ self.info_page_layout = QtWidgets.QHBoxLayout(self.info_page)
+ self.info_page_layout.setContentsMargins(0, 0, 0, 0)
+ self.info_page_layout.setObjectName("info_page_layout")
+ self.info_label_layout = QtWidgets.QFormLayout()
+ self.info_label_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.info_label_layout.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
+ self.info_label_layout.setObjectName("info_label_layout")
+ self.version_label = QtWidgets.QLabel(self.info_page)
+ self.version_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.version_label.setObjectName("version_label")
+ self.info_label_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.version_label)
+ self.path_label = QtWidgets.QLabel(self.info_page)
+ self.path_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.path_label.setObjectName("path_label")
+ self.info_label_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.path_label)
+ self.info_page_layout.addLayout(self.info_label_layout)
+ self.info_button_layout = QtWidgets.QVBoxLayout()
+ self.info_button_layout.setObjectName("info_button_layout")
+ self.update_button = QtWidgets.QPushButton(self.info_page)
+ self.update_button.setMinimumSize(QtCore.QSize(140, 0))
+ self.update_button.setObjectName("update_button")
+ self.info_button_layout.addWidget(self.update_button)
+ self.uninstall_button = QtWidgets.QPushButton(self.info_page)
+ self.uninstall_button.setMinimumSize(QtCore.QSize(140, 0))
+ self.uninstall_button.setObjectName("uninstall_button")
+ self.info_button_layout.addWidget(self.uninstall_button)
+ self.info_page_layout.addLayout(self.info_button_layout)
+ self.info_page_layout.setStretch(0, 1)
+ self.overlay_stack.addWidget(self.info_page)
self.eos_layout.addWidget(self.overlay_stack)
- self.enable_frame = QtWidgets.QFrame(EosWidget)
- self.enable_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
- self.enable_frame.setFrameShadow(QtWidgets.QFrame.Raised)
- self.enable_frame.setObjectName("enable_frame")
- self.enable_layout = QtWidgets.QVBoxLayout(self.enable_frame)
- self.enable_layout.setObjectName("enable_layout")
- self.select_pfx_combo = QtWidgets.QComboBox(self.enable_frame)
- self.select_pfx_combo.setObjectName("select_pfx_combo")
- self.enable_layout.addWidget(self.select_pfx_combo)
- self.enabled_cb = QtWidgets.QCheckBox(self.enable_frame)
- self.enabled_cb.setObjectName("enabled_cb")
- self.enable_layout.addWidget(self.enabled_cb)
- self.enabled_info_label = QtWidgets.QLabel(self.enable_frame)
- font = QtGui.QFont()
- font.setItalic(True)
- self.enabled_info_label.setFont(font)
- self.enabled_info_label.setText("")
- self.enabled_info_label.setObjectName("enabled_info_label")
- self.enable_layout.addWidget(self.enabled_info_label)
- self.eos_layout.addWidget(self.enable_frame)
self.retranslateUi(EosWidget)
self.overlay_stack.setCurrentIndex(0)
@@ -117,14 +86,13 @@ class Ui_EosWidget(object):
def retranslateUi(self, EosWidget):
_translate = QtCore.QCoreApplication.translate
EosWidget.setTitle(_translate("EosWidget", "Epic Overlay"))
- self.installed_version_info_lbl.setText(_translate("EosWidget", "Version"))
- self.installed_path_info_lbl.setText(_translate("EosWidget", "Location"))
- self.uninstall_button.setText(_translate("EosWidget", "Uninstall"))
- self.update_check_button.setText(_translate("EosWidget", "Check for update"))
- self.update_button.setText(_translate("EosWidget", "Update"))
- self.label.setText(_translate("EosWidget", "Epic Overlay Services is not installed"))
+ self.install_label.setText(_translate("EosWidget", "Status:"))
+ self.install_text.setText(_translate("EosWidget", "Epic Online Services Overlay is not installed"))
self.install_button.setText(_translate("EosWidget", "Install"))
- self.enabled_cb.setText(_translate("EosWidget", "Activated"))
+ self.version_label.setText(_translate("EosWidget", "Version:"))
+ self.path_label.setText(_translate("EosWidget", "Path:"))
+ self.update_button.setText(_translate("EosWidget", "Update"))
+ self.uninstall_button.setText(_translate("EosWidget", "Uninstall"))
if __name__ == "__main__":
diff --git a/rare/ui/components/tabs/games/integrations/eos_widget.ui b/rare/ui/components/tabs/games/integrations/eos_widget.ui
index 84e017bf..81dad003 100644
--- a/rare/ui/components/tabs/games/integrations/eos_widget.ui
+++ b/rare/ui/components/tabs/games/integrations/eos_widget.ui
@@ -6,179 +6,73 @@
0
0
- 586
- 146
+ 464
+ 98
-
-
- 0
- 0
-
-
GroupBox
Epic Overlay
-
+
QLayout::SetDefaultConstraint
-
-
- QFrame::StyledPanel
-
-
- QFrame::Raised
-
0
-
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ 0
-
- Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+ 0
-
-
-
-
- Version
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- -
-
-
- error
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
- -
-
-
- Location
-
-
-
- -
-
-
- error
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
- 120
- 0
-
-
+
-
+
- Uninstall
+ Status:
- -
-
-
-
- 120
- 0
-
-
+
-
+
- Check for update
-
-
-
- -
-
-
-
- 120
- 0
-
-
-
- Update
+ Epic Online Services Overlay is not installed
- -
-
-
- Qt::Vertical
+
-
+
+
+ 0
-
-
- 6
- 6
-
-
-
-
-
-
-
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
-
- -
-
-
- Epic Overlay Services is not installed
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
- 120
+ 140
0
@@ -189,57 +83,86 @@
- -
-
-
- Qt::Vertical
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- 6
- 6
-
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
+
-
+
+
+ Version:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Path:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 140
+ 0
+
+
+
+ Update
+
+
+
+ -
+
+
+
+ 140
+ 0
+
+
+
+ Uninstall
+
+
+
+
- -
-
-
- QFrame::StyledPanel
-
-
- QFrame::Raised
-
-
-
-
-
-
- -
-
-
- Activated
-
-
-
- -
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
From 3c43da15945b908438ed7ad28951b4a6b1a36c4e Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sun, 10 Sep 2023 19:08:47 +0300
Subject: [PATCH 11/29] RareCore: Add options to exclude non-asset games and
entitlements from startup
* Add options in settings to not request non-asset games and entitlements.
This greatly improves startup time but reduces functionality a bit. Both
options are disabled by default.
* Remove the old options to exclude Win32 and Mac games as they were no longer relevant.
* Add a performance context manager to log execution time when used.
* Fixed an issue with UbisoftGroup where multiple threads would spawn.
---
rare/components/tabs/settings/legendary.py | 13 +++
rare/shared/rare_core.py | 33 ++++---
rare/shared/workers/fetch.py | 98 +++++++++++--------
rare/ui/components/tabs/settings/legendary.py | 16 ++-
rare/ui/components/tabs/settings/legendary.ui | 26 ++++-
5 files changed, 127 insertions(+), 59 deletions(-)
diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py
index c4254608..2b08a01b 100644
--- a/rare/components/tabs/settings/legendary.py
+++ b/rare/components/tabs/settings/legendary.py
@@ -116,6 +116,19 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked())
)
+ self.exclude_non_asset_check.setChecked(
+ self.settings.value("exclude_non_asset", False, bool)
+ )
+ self.exclude_non_asset_check.stateChanged.connect(
+ lambda: self.settings.setValue("exclude_non_asset", self.exclude_non_asset_check.isChecked())
+ )
+ self.exclude_entitlements_check.setChecked(
+ self.settings.value("exclude_entitlements", False, bool)
+ )
+ self.exclude_entitlements_check.stateChanged.connect(
+ lambda: self.settings.setValue("exclude_entitlements", self.exclude_entitlements_check.isChecked())
+ )
+
self.refresh_metadata_button.clicked.connect(self.refresh_metadata)
# FIXME: Disable the button for now because it interferes with RareCore
self.refresh_metadata_button.setEnabled(False)
diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py
index 31a80f06..3582304a 100644
--- a/rare/shared/rare_core.py
+++ b/rare/shared/rare_core.py
@@ -9,12 +9,13 @@ from typing import Dict, Iterator, Callable, Optional, List, Union, Iterable, Tu
from PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool, QRunnable, QTimer
from legendary.lfs.eos import EOSOverlayApp
from legendary.models.game import Game, SaveGameFile
-from requests import HTTPError
+from requests.exceptions import HTTPError, ConnectionError
from rare.lgndr.core import LegendaryCore
from rare.models.base_game import RareSaveGame
from rare.models.game import RareGame, RareEosOverlay
from rare.models.signals import GlobalSignals
+from rare.utils.metrics import timelogger
from .image_manager import ImageManager
from .workers import (
QueueWorker,
@@ -50,7 +51,7 @@ class RareCore(QObject):
self.__core: Optional[LegendaryCore] = None
self.__image_manager: Optional[ImageManager] = None
- self.__start_time = time.time()
+ self.__start_time = time.perf_counter()
self.args(args)
self.signals(init=True)
@@ -310,12 +311,12 @@ class RareCore(QObject):
self.progress.emit(15, self.tr("Preparing library"))
self.__add_games_and_dlcs(*result)
self.progress.emit(100, self.tr("Launching Rare"))
- logger.debug(f"Fetch time {time.time() - self.__start_time} seconds")
+ logger.debug(f"Fetch time {time.perf_counter() - self.__start_time} seconds")
QTimer.singleShot(100, self.__post_init)
self.completed.emit()
def fetch(self):
- self.__start_time = time.time()
+ self.__start_time = time.perf_counter()
fetch_worker = FetchWorker(self.__core, self.__args)
fetch_worker.signals.progress.connect(self.progress)
fetch_worker.signals.result.connect(self.__on_fetch_result)
@@ -323,10 +324,10 @@ class RareCore(QObject):
def fetch_saves(self):
def __fetch() -> None:
- start_time = time.time()
saves_dict: Dict[str, List[SaveGameFile]] = {}
try:
- saves_list = self.__core.get_save_games()
+ with timelogger(logger, "Request saves"):
+ saves_list = self.__core.get_save_games()
for s in saves_list:
if s.app_name not in saves_dict.keys():
saves_dict[s.app_name] = [s]
@@ -337,26 +338,28 @@ class RareCore(QObject):
continue
self.__library[app_name].load_saves(saves)
except (HTTPError, ConnectionError) as e:
- logger.error(f"Exception while fetching saves from EGS: {e}")
+ logger.error(f"Exception while fetching saves from EGS.")
+ logger.error(e)
return
- logger.debug(f"Saves: {len(saves_dict)}")
- logger.debug(f"Request saves: {time.time() - start_time} seconds")
+ logger.info(f"Saves: {len(saves_dict)}")
saves_worker = QRunnable.create(__fetch)
QThreadPool.globalInstance().start(saves_worker)
def fetch_entitlements(self) -> None:
def __fetch() -> None:
- start_time = time.time()
try:
- entitlements = self.__core.egs.get_user_entitlements()
+ if (entitlements := self.__core.lgd.entitlements) is None:
+ with timelogger(logger, "Request entitlements"):
+ entitlements = self.__core.egs.get_user_entitlements()
+ self.__core.lgd.entitlements = entitlements
for game in self.__library.values():
game.grant_date()
except (HTTPError, ConnectionError) as e:
- logger.error(f"Failed to retrieve user entitlements from EGS: {e}")
+ logger.error(f"Exception while fetching entitlements from EGS.")
+ logger.error(e)
return
- logger.debug(f"Entitlements: {len(list(entitlements))}")
- logger.debug(f"Request Entitlements: {time.time() - start_time} seconds")
+ logger.info(f"Entitlements: {len(list(entitlements))}")
entitlements_worker = QRunnable.create(__fetch)
QThreadPool.globalInstance().start(entitlements_worker)
@@ -368,7 +371,7 @@ class RareCore(QObject):
def __post_init(self) -> None:
if not self.__args.offline:
self.fetch_saves()
- self.fetch_entitlements()
+ # self.fetch_entitlements()
self.resolve_origin()
@property
diff --git a/rare/shared/workers/fetch.py b/rare/shared/workers/fetch.py
index b18ea83f..a1173ad5 100644
--- a/rare/shared/workers/fetch.py
+++ b/rare/shared/workers/fetch.py
@@ -1,13 +1,13 @@
import platform
-import time
from argparse import Namespace
from enum import IntEnum
from logging import getLogger
from PyQt5.QtCore import QObject, pyqtSignal, QSettings
-from requests.exceptions import ConnectionError, HTTPError
+from requests.exceptions import HTTPError, ConnectionError
from rare.lgndr.core import LegendaryCore
+from rare.utils.metrics import timelogger
from .worker import Worker
logger = getLogger("FetchWorker")
@@ -32,13 +32,11 @@ class FetchWorker(Worker):
def run_real(self):
# Fetch regular EGL games with assets
- start_time = time.time()
-
want_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug
want_win32 = self.settings.value("win32_meta", False, bool)
want_macos = self.settings.value("macos_meta", False, bool)
need_macos = platform.system() == "Darwin"
- need_windows = not any([want_win32, want_macos, need_macos, self.args.debug])
+ need_windows = not any([want_win32, want_macos, need_macos, self.args.debug]) and not self.args.offline
if want_win32 or self.args.debug:
logger.info(
@@ -47,9 +45,10 @@ class FetchWorker(Worker):
"with" if want_unreal else "without"
)
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows"))
- self.core.get_game_and_dlc_list(
- update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal
- )
+ with timelogger(logger, "Request Win32 games"):
+ self.core.get_game_and_dlc_list(
+ update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal
+ )
if need_macos or want_macos or self.args.debug:
logger.info(
@@ -58,9 +57,10 @@ class FetchWorker(Worker):
"with" if want_unreal else "without"
)
self.signals.progress.emit(15, self.signals.tr("Updating game metadata for macOS"))
- self.core.get_game_and_dlc_list(
- update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal
- )
+ with timelogger(logger, "Request macOS games"):
+ self.core.get_game_and_dlc_list(
+ update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal
+ )
if need_windows:
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows"))
@@ -68,39 +68,53 @@ class FetchWorker(Worker):
"Requesting Windows metadata, %s Unreal engine",
"with" if want_unreal else "without"
)
- games, dlc_dict = self.core.get_game_and_dlc_list(
- update_assets=need_windows, platform="Windows", skip_ue=not want_unreal
- )
- logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}")
- logger.debug(f"Request games: {time.time() - start_time} seconds")
+ with timelogger(logger, "Request Windows games"):
+ games, dlc_dict = self.core.get_game_and_dlc_list(
+ update_assets=need_windows, platform="Windows", skip_ue=not want_unreal
+ )
+ logger.info(f"Games: %s. Games with DLCs: %s", len(games), len(dlc_dict))
+
+ want_non_asset = not self.settings.value("exclude_non_asset", False, bool)
+ want_entitlements = not self.settings.value("exclude_entitlements", False, bool)
# Fetch non-asset games
- self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
- start_time = time.time()
- try:
- na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
- except (HTTPError, ConnectionError) as e:
- logger.warning(f"Exception while fetching non asset games from EGS: {e}")
- na_games, na_dlc_dict = ([], {})
- # FIXME:
- # This is here because of broken appIds from Epic:
- # https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729
- # There is a tab character in the appId of Fallout New Vegas: Honest Hearts DLC, this breaks metadata storage
- # on Windows as they can't handle tabs at the end of the filename (?)
- # Legendary and Heroic are also affected, but it completely breaks Rare, so dodge it for now pending a fix.
- except Exception as e:
- logger.error(f"Exception while fetching non asset games from EGS: {e}")
- na_games, na_dlc_dict = ([], {})
- logger.debug(f"Non-asset {len(na_games)}, games with non-asset DLCs {len(na_dlc_dict)}")
- logger.debug(f"Request non-asset: {time.time() - start_time} seconds")
+ if want_non_asset:
+ self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
+ try:
+ with timelogger(logger, "Request non-asset"):
+ na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
+ except (HTTPError, ConnectionError) as e:
+ logger.error("Network exception while fetching non asset games from EGS.")
+ logger.error(e)
+ na_games, na_dlc_dict = ([], {})
+ # FIXME:
+ # This is here because of broken appIds from Epic:
+ # https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729
+ # There is a tab character in the appId of Fallout New Vegas: Honest Hearts DLC, this breaks metadata storage
+ # on Windows as they can't handle tabs at the end of the filename (?)
+ # Legendary and Heroic are also affected, but it completely breaks Rare, so dodge it for now pending a fix.
+ except Exception as e:
+ logger.error("General exception while fetching non asset games from EGS.")
+ logger.error(e)
+ na_games, na_dlc_dict = ([], {})
+ logger.info("Non-asset: %s. Non-asset with DLCs: %s", len(na_games), len(na_dlc_dict))
- # Combine the two games lists and the two dlc dictionaries between regular and non-asset results
- games += na_games
- for catalog_id, dlcs in na_dlc_dict.items():
- if catalog_id in dlc_dict.keys():
- dlc_dict[catalog_id] += dlcs
- else:
- dlc_dict[catalog_id] = dlcs
- logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}")
+ # Combine the two games lists and the two dlc dictionaries between regular and non-asset results
+ games += na_games
+ for catalog_id, dlcs in na_dlc_dict.items():
+ if catalog_id in dlc_dict.keys():
+ dlc_dict[catalog_id] += dlcs
+ else:
+ dlc_dict[catalog_id] = dlcs
+ logger.info(f"Games: {len(games)}. Games with DLCs: {len(dlc_dict)}")
+ if want_entitlements:
+ # Get entitlements, Ubisoft integration also uses them
+ self.signals.progress.emit(40, self.signals.tr("Updating entitlements"))
+ with timelogger(logger, "Request entitlements"):
+ entitlements = self.core.egs.get_user_entitlements()
+ self.core.lgd.entitlements = entitlements
+ logger.info(f"Entitlements: {len(list(entitlements))}")
+
+ self.signals.progress.emit(50, self.signals.tr("Preparing games"))
self.signals.result.emit((games, dlc_dict), FetchWorker.Result.COMBINED)
diff --git a/rare/ui/components/tabs/settings/legendary.py b/rare/ui/components/tabs/settings/legendary.py
index 829a60ab..a8306397 100644
--- a/rare/ui/components/tabs/settings/legendary.py
+++ b/rare/ui/components/tabs/settings/legendary.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/legendary.ui'
#
-# Created by: PyQt5 UI code generator 5.15.10
+# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -139,6 +139,12 @@ class Ui_LegendarySettings(object):
self.fetch_unreal_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_unreal_check.setObjectName("fetch_unreal_check")
self.verticalLayout_2.addWidget(self.fetch_unreal_check)
+ self.exclude_non_asset_check = QtWidgets.QCheckBox(self.meta_group)
+ self.exclude_non_asset_check.setObjectName("exclude_non_asset_check")
+ self.verticalLayout_2.addWidget(self.exclude_non_asset_check)
+ self.exclude_entitlements_check = QtWidgets.QCheckBox(self.meta_group)
+ self.exclude_entitlements_check.setObjectName("exclude_entitlements_check")
+ self.verticalLayout_2.addWidget(self.exclude_entitlements_check)
self.metadata_info = QtWidgets.QLabel(self.metadata_group)
font = QtGui.QFont()
font.setItalic(True)
@@ -175,6 +181,14 @@ class Ui_LegendarySettings(object):
self.fetch_win32_check.setText(_translate("LegendarySettings", "Include Win32 games"))
self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games"))
self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine"))
+ self.exclude_non_asset_check.setToolTip(_translate("LegendarySettings", "Do not load metadata for non-asset games (i.e. Origin games) on start-up.\n"
+ "\n"
+ "Disabling this greatly improves start-up time, but some games might not be visible in your library."))
+ self.exclude_non_asset_check.setText(_translate("LegendarySettings", "Exclude non-asset games"))
+ self.exclude_entitlements_check.setToolTip(_translate("LegendarySettings", "Do not load entitlement data (i.e game\'s date of purchase) on start-up.\n"
+ "\n"
+ "Disabling this greatly improves start-up time, but some library filters may not work."))
+ self.exclude_entitlements_check.setText(_translate("LegendarySettings", "Exclude entitlements"))
self.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply"))
self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata"))
diff --git a/rare/ui/components/tabs/settings/legendary.ui b/rare/ui/components/tabs/settings/legendary.ui
index 65e69f1c..9f1a9c4c 100644
--- a/rare/ui/components/tabs/settings/legendary.ui
+++ b/rare/ui/components/tabs/settings/legendary.ui
@@ -231,7 +231,7 @@
Platforms
-
+
-
@@ -253,6 +253,30 @@
+ -
+
+
+ Do not load metadata for non-asset games (i.e. Origin games) on start-up.
+
+Disabling this greatly improves start-up time, but some games might not be visible in your library.
+
+
+ Exclude non-asset games
+
+
+
+ -
+
+
+ Do not load entitlement data (i.e game's date of purchase) on start-up.
+
+Disabling this greatly improves start-up time, but some library filters may not work.
+
+
+ Exclude entitlements
+
+
+
-
From 89486f882e1749659e2c2cff18179b5c71431511 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sun, 10 Sep 2023 19:11:03 +0300
Subject: [PATCH 12/29] EOSGroup: Add the option to select which overlay
installation to enable
---
.../tabs/games/integrations/eos_group.py | 157 +++++++++---------
rare/models/game.py | 32 +++-
rare/shared/workers/uninstall.py | 2 +-
3 files changed, 107 insertions(+), 84 deletions(-)
diff --git a/rare/components/tabs/games/integrations/eos_group.py b/rare/components/tabs/games/integrations/eos_group.py
index 154c820f..7cd8c163 100644
--- a/rare/components/tabs/games/integrations/eos_group.py
+++ b/rare/components/tabs/games/integrations/eos_group.py
@@ -4,8 +4,17 @@ from logging import getLogger
from typing import Optional
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, Qt, pyqtSlot, QSize
-from PyQt5.QtWidgets import QGroupBox, QMessageBox, QFrame, QHBoxLayout, QSizePolicy, QLabel, QPushButton, QFormLayout
-from legendary.lfs import eos
+from PyQt5.QtWidgets import (
+ QGroupBox,
+ QMessageBox,
+ QFrame,
+ QHBoxLayout,
+ QSizePolicy,
+ QLabel,
+ QPushButton,
+ QFormLayout,
+ QComboBox,
+)
from rare.lgndr.core import LegendaryCore
from rare.models.game import RareEosOverlay
@@ -42,55 +51,101 @@ class EosPrefixWidget(QFrame):
self.indicator = QLabel(parent=self)
self.indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
- self.label = ElideLabel(
- prefix if prefix is not None else "Epic Online Services Overlay",
- parent=self
- )
+ self.prefix_label = ElideLabel(prefix if prefix is not None else overlay.app_title, parent=self)
+ self.overlay_label = ElideLabel(parent=self)
+ self.overlay_label.setDisabled(True)
+
+ self.path_select = QComboBox(self)
+ self.path_select.setMaximumWidth(150)
+ self.path_select.setMinimumWidth(150)
self.button = QPushButton(parent=self)
self.button.setMinimumWidth(150)
- self.button.clicked.connect(self.action)
layout = QHBoxLayout(self)
layout.setContentsMargins(-1, 0, 0, 0)
layout.addWidget(self.indicator)
- layout.addWidget(self.label, stretch=1)
+ layout.addWidget(self.prefix_label, stretch=2)
+ layout.addWidget(self.overlay_label, stretch=3)
+ layout.addWidget(self.path_select)
layout.addWidget(self.button)
self.overlay = overlay
self.prefix = prefix
+ self.path_select.currentIndexChanged.connect(self.path_changed)
+ self.button.clicked.connect(self.action)
self.overlay.signals.game.installed.connect(self.update_state)
self.overlay.signals.game.uninstalled.connect(self.update_state)
self.update_state()
+ @pyqtSlot(int)
+ def path_changed(self, index: int) -> None:
+ path = self.path_select.itemData(index, Qt.UserRole)
+ active_path = os.path.normpath(p) if (p := self.overlay.active_path(self.prefix)) else ""
+ if self.overlay.is_enabled(self.prefix) and (path == active_path):
+ self.button.setText(self.tr("Disable overlay"))
+ else:
+ self.button.setText(self.tr("Enable overlay"))
+
@pyqtSlot()
- def update_state(self):
- if not self.overlay.is_installed:
+ def update_state(self) -> None:
+ active_path = os.path.normpath(p) if (p := self.overlay.active_path(self.prefix)) else ""
+
+ self.overlay_label.setText(f"{active_path}<\i>")
+ self.path_select.clear()
+
+ if not self.overlay.is_installed and not self.overlay.available_paths(self.prefix):
self.setDisabled(True)
- self.button.setText(self.tr("Unavailable"))
self.indicator.setPixmap(icon("fa.circle-o", color="grey").pixmap(20, 20))
+ self.overlay_label.setText(self.overlay.active_path(self.prefix))
+ self.button.setText(self.tr("Unavailable"))
return
self.setDisabled(False)
+
if self.overlay.is_enabled(self.prefix):
- self.button.setText(self.tr("Disable overlay"))
- self.indicator.setPixmap(
- icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20))
- )
+ self.indicator.setPixmap(icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20)))
else:
- self.button.setText(self.tr("Enable overlay"))
- self.indicator.setPixmap(
- icon("fa.times-circle-o", color="red").pixmap(QSize(20, 20))
- )
+ self.indicator.setPixmap(icon("fa.times-circle-o", color="red").pixmap(QSize(20, 20)))
+
+ install_path = os.path.normpath(p) if (p := self.overlay.install_path) else ""
+
+ self.path_select.addItem("Auto-detect", "")
+ self.path_select.setItemData(0, "Auto-detect", Qt.ToolTipRole)
+ for path in self.overlay.available_paths(self.prefix):
+ path = os.path.normpath(path)
+ self.path_select.addItem("Legendary-managed" if path == install_path else "EGL-managed", path)
+ self.path_select.setItemData(self.path_select.findData(path), path, Qt.ToolTipRole)
+ self.path_select.setCurrentIndex(self.path_select.findData(active_path))
@pyqtSlot()
- def action(self):
- if self.overlay.is_enabled(self.prefix):
- self.overlay.disable(prefix=self.prefix)
+ def action(self) -> None:
+ path = self.path_select.currentData(Qt.UserRole)
+ active_path = os.path.normpath(p) if (p := self.overlay.active_path(self.prefix)) else ""
+ install_path = os.path.normpath(p) if (p := self.overlay.install_path) else ""
+ if self.overlay.is_enabled(self.prefix) and (path == active_path):
+ if not self.overlay.disable(prefix=self.prefix):
+ QMessageBox.warning(
+ self, "Warning",
+ self.tr("Failed to completely disable the active EOS Overlay.{}").format(
+ self.tr(" Since the previous overlay was managed by EGL you can safely ignore this is.")
+ if active_path != install_path else ""
+ ),
+ )
else:
- self.overlay.enable(prefix=self.prefix)
+ self.overlay.disable(prefix=self.prefix)
+ if not self.overlay.enable(prefix=self.prefix, path=path):
+ QMessageBox.warning(
+ self,
+ "Warning",
+ self.tr("Failed to completely enable EOS overlay.{}").format(
+ self.tr(" Since the previous overlay was managed by EGL you can safely ignore this is.")
+ if active_path != install_path
+ else ""
+ ),
+ )
self.update_state()
@@ -129,7 +184,7 @@ class EosGroup(QGroupBox):
if self.overlay.is_installed: # installed
self.installed_version_label.setText(f"{self.overlay.version}")
- self.installed_path_label.setText(self.overlay.install_path)
+ self.installed_path_label.setText(os.path.normpath(self.overlay.install_path))
self.ui.overlay_stack.setCurrentWidget(self.ui.info_page)
else:
self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
@@ -185,65 +240,13 @@ class EosGroup(QGroupBox):
def uninstall_finished(self):
self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
- def change_enable(self):
- enabled = self.ui.enabled_cb.isChecked()
- if not enabled:
- try:
- eos.remove_registry_entries(self.current_prefix)
- except PermissionError:
- logger.error("Can't disable eos overlay")
- QMessageBox.warning(self, "Error", self.tr(
- "Failed to disable Overlay. Probably it is installed by Epic Games Launcher"))
- return
- logger.info("Disabled Epic Overlay")
- self.ui.enabled_info_label.setText(self.tr("Disabled"))
- else:
- if not self.overlay.is_installed:
- available_installs = self.core.search_overlay_installs(self.current_prefix)
- if not available_installs:
- logger.error('No EOS overlay installs found!')
- return
- path = available_installs[0]
- else:
- path = self.overlay.install_path
-
- if not self.core.is_overlay_install(path):
- logger.error(f'Not a valid Overlay installation: {path}')
- self.ui.select_pfx_combo.removeItem(self.ui.select_pfx_combo.currentIndex())
- return
-
- path = os.path.normpath(path)
- reg_paths = eos.query_registry_entries(self.current_prefix)
- if old_path := reg_paths["overlay_path"]:
- if os.path.normpath(old_path) == path:
- logger.info(f'Overlay already enabled, nothing to do.')
- return
- else:
- logger.info(f'Updating overlay registry entries from "{old_path}" to "{path}"')
- try:
- eos.remove_registry_entries(self.current_prefix)
- except PermissionError:
- logger.error("Can't disable eos overlay")
- QMessageBox.warning(self, "Error", self.tr(
- "Failed to disable Overlay. Probably it is installed by Epic Games Launcher"))
- return
- try:
- eos.add_registry_entries(path, self.current_prefix)
- except PermissionError:
- logger.error("Failed to disable eos overlay")
- QMessageBox.warning(self, "Error", self.tr(
- "Failed to enable EOS overlay. Maybe it is already installed by Epic Games Launcher"))
- return
- self.ui.enabled_info_label.setText(self.tr("Enabled"))
- logger.info(f'Enabled overlay at: {path}')
-
@pyqtSlot()
def install_overlay(self):
self.overlay.install()
def uninstall_overlay(self):
if not self.overlay.is_installed:
- logger.error('No Rare-managed overlay installation found.')
+ logger.error("No Legendary-managed overlay installation found.")
self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
return
self.overlay.uninstall()
diff --git a/rare/models/game.py b/rare/models/game.py
index f45b8709..557a8549 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -583,13 +583,23 @@ class RareEosOverlay(RareGameBase):
reg_paths = eos.query_registry_entries(prefix)
return reg_paths["overlay_path"] and self.core.is_overlay_install(reg_paths["overlay_path"])
+ def active_path(self, prefix: Optional[str] = None) -> str:
+ path = eos.query_registry_entries(prefix)["overlay_path"]
+ return path if path and self.core.is_overlay_install(path) else ""
+
+ def available_paths(self, prefix: Optional[str] = None) -> List[str]:
+ return self.core.search_overlay_installs(prefix)
+
def enable(
- self, prefix: Optional[str] = None, app_name: Optional[str] = None, path: Optional[str] = None
+ self, prefix: Optional[str] = None, path: Optional[str] = None
) -> bool:
- if not self.is_installed or self.is_enabled(prefix):
+ if self.is_enabled(prefix):
return False
if not path:
- path = self.igame.install_path
+ if self.is_installed:
+ path = self.igame.install_path
+ else:
+ path = self.available_paths(prefix)[-1]
reg_paths = eos.query_registry_entries(prefix)
if old_path := reg_paths["overlay_path"]:
if os.path.normpath(old_path) == path:
@@ -598,15 +608,25 @@ class RareEosOverlay(RareGameBase):
else:
logger.info(f'Updating overlay registry entries from "{old_path}" to "{path}"')
eos.remove_registry_entries(prefix)
- eos.add_registry_entries(path, prefix)
+ try:
+ eos.add_registry_entries(path, prefix)
+ except PermissionError as e:
+ logger.error("Exception while writing registry to enable the overlay .")
+ logger.error(e)
+ return False
logger.info(f"Enabled overlay at: {path} for prefix: {prefix}")
return True
- def disable(self, prefix: Optional[str] = None, app_name: Optional[str] = None) -> bool:
+ def disable(self, prefix: Optional[str] = None) -> bool:
if not self.is_enabled(prefix):
return False
logger.info(f"Disabling overlay (removing registry keys) for prefix: {prefix}")
- eos.remove_registry_entries(prefix)
+ try:
+ eos.remove_registry_entries(prefix)
+ except PermissionError as e:
+ logger.error("Exception while writing registry to disable the overlay.")
+ logger.error(e)
+ return False
return True
def install(self) -> bool:
diff --git a/rare/shared/workers/uninstall.py b/rare/shared/workers/uninstall.py
index 9b154ce8..98fe21b7 100644
--- a/rare/shared/workers/uninstall.py
+++ b/rare/shared/workers/uninstall.py
@@ -40,7 +40,7 @@ def uninstall_game(
remove_registry_entries(prefix)
logger.debug("Removed registry entries for prefix %s", prefix)
else:
- remove_registry_entries(None)
+ remove_registry_entries()
return True, ""
From 889a7cd116a3fd90df4dd8d5f205c105e1374bad Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Wed, 13 Sep 2023 20:50:59 +0300
Subject: [PATCH 13/29] RareGame: Use the callable directly instead of a lambda
to create workers.
---
rare/models/base_game.py | 4 ++--
rare/models/game.py | 12 ++++++++----
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/rare/models/base_game.py b/rare/models/base_game.py
index 0cd1611c..ab34b466 100644
--- a/rare/models/base_game.py
+++ b/rare/models/base_game.py
@@ -268,7 +268,7 @@ class RareGameSlim(RareGameBase):
return
if thread:
- worker = QRunnable.create(lambda: _upload())
+ worker = QRunnable.create(_upload)
QThreadPool.globalInstance().start(worker)
else:
_upload()
@@ -293,7 +293,7 @@ class RareGameSlim(RareGameBase):
return
if thread:
- worker = QRunnable.create(lambda: _download())
+ worker = QRunnable.create(_download)
QThreadPool.globalInstance().start(worker)
else:
_download()
diff --git a/rare/models/game.py b/rare/models/game.py
index 557a8549..80c90457 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -434,9 +434,12 @@ class RareGame(RareGameSlim):
elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date)
if self.metadata.steam_grade is not None and elapsed_time.days < 3:
return self.metadata.steam_grade
- worker = QRunnable.create(
- lambda: self.set_steam_grade(get_rating(self.core, self.app_name))
- )
+
+ def _set_steam_grade():
+ rating = get_rating(self.core, self.app_name)
+ self.set_steam_grade(rating)
+
+ worker = QRunnable.create(_set_steam_grade)
QThreadPool.globalInstance().start(worker)
return "pending"
@@ -447,9 +450,10 @@ class RareGame(RareGameSlim):
self.signals.widget.update.emit()
def grant_date(self, force=False) -> datetime:
+ if (entitlements := self.core.lgd.entitlements) is None:
+ return self.metadata.grant_date
if self.metadata.grant_date is None or force:
logger.debug("Grant date for %s not found in metadata, resolving", self.app_name)
- entitlements = self.core.lgd.entitlements
matching = filter(lambda ent: ent["namespace"] == self.game.namespace, entitlements)
entitlement = next(matching, None)
grant_date = datetime.fromisoformat(
From acbe8836cc8895f947c10532b0938d3870e41a8c Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Wed, 13 Sep 2023 21:23:01 +0300
Subject: [PATCH 14/29] RareEosOverlay: Protect against invalid prefixes
---
rare/models/game.py | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/rare/models/game.py b/rare/models/game.py
index 80c90457..0c305089 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -583,16 +583,29 @@ class RareEosOverlay(RareGameBase):
self.core.check_for_overlay_updates()
return self.core.overlay_update_available
- def is_enabled(self, prefix: Optional[str] = None):
- reg_paths = eos.query_registry_entries(prefix)
+ def is_enabled(self, prefix: Optional[str] = None) -> bool:
+ try:
+ reg_paths = eos.query_registry_entries(prefix)
+ except ValueError as e:
+ logger.info("%s %s", e, prefix)
+ return False
return reg_paths["overlay_path"] and self.core.is_overlay_install(reg_paths["overlay_path"])
def active_path(self, prefix: Optional[str] = None) -> str:
- path = eos.query_registry_entries(prefix)["overlay_path"]
+ try:
+ path = eos.query_registry_entries(prefix)["overlay_path"]
+ except ValueError as e:
+ logger.info("%s %s", e, prefix)
+ return ""
return path if path and self.core.is_overlay_install(path) else ""
def available_paths(self, prefix: Optional[str] = None) -> List[str]:
- return self.core.search_overlay_installs(prefix)
+ try:
+ installs = self.core.search_overlay_installs(prefix)
+ except ValueError as e:
+ logger.info("%s %s", e, prefix)
+ return []
+ return installs
def enable(
self, prefix: Optional[str] = None, path: Optional[str] = None
From 7ae06ff5d8312ea2a3325070cbb13f5e9cb1afe8 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 15 Sep 2023 00:40:28 +0300
Subject: [PATCH 15/29] RareCore: Move entitlements request into an independent
worker
Yes, we are back at this. Entitlements are important to have early
as Ubisoft redemption requires them, and they don't depend on anything
else.
* Move config helper initialization into RareCore to make it available
earlier.
---
rare/components/__init__.py | 2 +-
rare/shared/rare_core.py | 73 ++++++++++++++++++---------------
rare/shared/workers/__init__.py | 2 +-
rare/shared/workers/fetch.py | 58 +++++++++++++++-----------
4 files changed, 78 insertions(+), 57 deletions(-)
diff --git a/rare/components/__init__.py b/rare/components/__init__.py
index e76dacf1..f4c9cce4 100644
--- a/rare/components/__init__.py
+++ b/rare/components/__init__.py
@@ -12,7 +12,7 @@ from requests import HTTPError
from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow
from rare.shared import RareCore
-from rare.utils import config_helper, paths
+from rare.utils import paths
from rare.utils.misc import ExitCodes
from rare.widgets.rare_app import RareApp, RareAppException
diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py
index 3582304a..9afcaac2 100644
--- a/rare/shared/rare_core.py
+++ b/rare/shared/rare_core.py
@@ -16,12 +16,15 @@ from rare.models.base_game import RareSaveGame
from rare.models.game import RareGame, RareEosOverlay
from rare.models.signals import GlobalSignals
from rare.utils.metrics import timelogger
+from rare.utils import config_helper
from .image_manager import ImageManager
from .workers import (
QueueWorker,
VerifyWorker,
MoveWorker,
FetchWorker,
+ GamesDlcsWorker,
+ EntitlementsWorker,
OriginWineWorker,
)
from .workers.uninstall import uninstall_game
@@ -70,6 +73,10 @@ class RareCore(QObject):
self.__eos_overlay.signals.game.install.connect(self.__signals.game.install)
self.__eos_overlay.signals.game.uninstall.connect(self.__signals.game.uninstall)
+ self.__fetch_progress: int = 0
+ self.__fetched_games_dlcs: bool = False
+ self.__fetched_entitlements: bool = False
+
RareCore.__instance = self
def enqueue_worker(self, rgame: RareGame, worker: QueueWorker):
@@ -303,24 +310,45 @@ class RareCore(QObject):
logger.info(f'Marking "{rgame.app_title}" as not installed because an exception has occurred...')
logger.error(e)
rgame.set_installed(False)
- self.progress.emit(int(idx/length * 80) + 20, self.tr("Loaded {}").format(rgame.app_title))
+ progress = int(idx/length * self.__fetch_progress) + (100 - self.__fetch_progress)
+ self.progress.emit(progress, self.tr("Loaded {}").format(rgame.app_title))
+
+ @pyqtSlot(int, str)
+ def __on_fetch_progress(self, increment: int, message: str):
+ self.__fetch_progress += increment
+ self.progress.emit(self.__fetch_progress, message)
@pyqtSlot(object, int)
- def __on_fetch_result(self, result: Tuple[List, Dict], res_type: int):
- logger.info(f"Got API results for {FetchWorker.Result(res_type).name}")
- self.progress.emit(15, self.tr("Preparing library"))
- self.__add_games_and_dlcs(*result)
- self.progress.emit(100, self.tr("Launching Rare"))
- logger.debug(f"Fetch time {time.perf_counter() - self.__start_time} seconds")
- QTimer.singleShot(100, self.__post_init)
- self.completed.emit()
+ def __on_fetch_result(self, result: Tuple, result_type: int):
+ if result_type == FetchWorker.Result.GAMESDLCS:
+ self.__add_games_and_dlcs(*result)
+ self.__fetched_games_dlcs = True
+
+ if result_type == FetchWorker.Result.ENTITLEMENTS:
+ self.__core.lgd.entitlements = result
+ self.__fetched_entitlements = True
+
+ logger.info(f"Acquired data for {FetchWorker.Result(result_type).name}")
+
+ if all([self.__fetched_games_dlcs, self.__fetched_entitlements]):
+ logger.debug(f"Fetch time {time.perf_counter() - self.__start_time} seconds")
+ self.progress.emit(100, self.tr("Launching Rare"))
+ self.completed.emit()
+ QTimer.singleShot(100, self.__post_init)
def fetch(self):
self.__start_time = time.perf_counter()
- fetch_worker = FetchWorker(self.__core, self.__args)
- fetch_worker.signals.progress.connect(self.progress)
- fetch_worker.signals.result.connect(self.__on_fetch_result)
- QThreadPool.globalInstance().start(fetch_worker)
+
+ games_dlcs_worker = GamesDlcsWorker(self.__core, self.__args)
+ games_dlcs_worker.signals.progress.connect(self.__on_fetch_progress)
+ games_dlcs_worker.signals.result.connect(self.__on_fetch_result)
+
+ entitlements_worker = EntitlementsWorker(self.__core, self.__args)
+ entitlements_worker.signals.progress.connect(self.__on_fetch_progress)
+ entitlements_worker.signals.result.connect(self.__on_fetch_result)
+
+ QThreadPool.globalInstance().start(games_dlcs_worker)
+ QThreadPool.globalInstance().start(entitlements_worker)
def fetch_saves(self):
def __fetch() -> None:
@@ -346,24 +374,6 @@ class RareCore(QObject):
saves_worker = QRunnable.create(__fetch)
QThreadPool.globalInstance().start(saves_worker)
- def fetch_entitlements(self) -> None:
- def __fetch() -> None:
- try:
- if (entitlements := self.__core.lgd.entitlements) is None:
- with timelogger(logger, "Request entitlements"):
- entitlements = self.__core.egs.get_user_entitlements()
- self.__core.lgd.entitlements = entitlements
- for game in self.__library.values():
- game.grant_date()
- except (HTTPError, ConnectionError) as e:
- logger.error(f"Exception while fetching entitlements from EGS.")
- logger.error(e)
- return
- logger.info(f"Entitlements: {len(list(entitlements))}")
-
- entitlements_worker = QRunnable.create(__fetch)
- QThreadPool.globalInstance().start(entitlements_worker)
-
def resolve_origin(self) -> None:
origin_worker = OriginWineWorker(self.__core, list(self.origin_games))
QThreadPool.globalInstance().start(origin_worker)
@@ -371,7 +381,6 @@ class RareCore(QObject):
def __post_init(self) -> None:
if not self.__args.offline:
self.fetch_saves()
- # self.fetch_entitlements()
self.resolve_origin()
@property
diff --git a/rare/shared/workers/__init__.py b/rare/shared/workers/__init__.py
index b11df089..12d816c0 100644
--- a/rare/shared/workers/__init__.py
+++ b/rare/shared/workers/__init__.py
@@ -1,4 +1,4 @@
-from .fetch import FetchWorker
+from .fetch import FetchWorker, GamesDlcsWorker, EntitlementsWorker
from .install_info import InstallInfoWorker
from .move import MoveWorker
from .uninstall import UninstallWorker
diff --git a/rare/shared/workers/fetch.py b/rare/shared/workers/fetch.py
index a1173ad5..1d4fe761 100644
--- a/rare/shared/workers/fetch.py
+++ b/rare/shared/workers/fetch.py
@@ -15,9 +15,9 @@ logger = getLogger("FetchWorker")
class FetchWorker(Worker):
class Result(IntEnum):
- GAMES = 1
- NON_ASSET = 2
- COMBINED = 3
+ ERROR = 0
+ GAMESDLCS = 1
+ ENTITLEMENTS = 2
class Signals(QObject):
progress = pyqtSignal(int, str)
@@ -30,7 +30,33 @@ class FetchWorker(Worker):
self.args = args
self.settings = QSettings()
+
+class EntitlementsWorker(FetchWorker):
+ def __init__(self, core: LegendaryCore, args: Namespace):
+ super(EntitlementsWorker, self).__init__(core, args)
+
def run_real(self):
+ entitlements = ()
+ want_entitlements = not self.settings.value("exclude_entitlements", False, bool)
+ if want_entitlements:
+ # Get entitlements, Ubisoft integration also uses them
+ self.signals.progress.emit(0, self.signals.tr("Updating entitlements"))
+ with timelogger(logger, "Request entitlements"):
+ entitlements = self.core.egs.get_user_entitlements()
+ self.core.lgd.entitlements = entitlements
+ logger.info(f"Entitlements: %s", len(list(entitlements)))
+ self.signals.result.emit(entitlements, FetchWorker.Result.ENTITLEMENTS)
+ return
+
+
+class GamesDlcsWorker(FetchWorker):
+
+ def __init__(self, core: LegendaryCore, args: Namespace):
+ super(GamesDlcsWorker, self).__init__(core, args)
+ self.exclude_non_asset = QSettings().value("exclude_non_asset", False, bool)
+
+ def run_real(self):
+
# Fetch regular EGL games with assets
want_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug
want_win32 = self.settings.value("win32_meta", False, bool)
@@ -74,25 +100,19 @@ class FetchWorker(Worker):
)
logger.info(f"Games: %s. Games with DLCs: %s", len(games), len(dlc_dict))
- want_non_asset = not self.settings.value("exclude_non_asset", False, bool)
- want_entitlements = not self.settings.value("exclude_entitlements", False, bool)
-
# Fetch non-asset games
+ want_non_asset = not self.settings.value("exclude_non_asset", False, bool)
if want_non_asset:
self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
try:
with timelogger(logger, "Request non-asset"):
na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
except (HTTPError, ConnectionError) as e:
- logger.error("Network exception while fetching non asset games from EGS.")
+ logger.error(f"Network error while fetching non asset games")
logger.error(e)
na_games, na_dlc_dict = ([], {})
- # FIXME:
- # This is here because of broken appIds from Epic:
- # https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729
- # There is a tab character in the appId of Fallout New Vegas: Honest Hearts DLC, this breaks metadata storage
- # on Windows as they can't handle tabs at the end of the filename (?)
- # Legendary and Heroic are also affected, but it completely breaks Rare, so dodge it for now pending a fix.
+ # NOTE: This is here because of broken appIds from Epic
+ # https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729
except Exception as e:
logger.error("General exception while fetching non asset games from EGS.")
logger.error(e)
@@ -108,13 +128,5 @@ class FetchWorker(Worker):
dlc_dict[catalog_id] = dlcs
logger.info(f"Games: {len(games)}. Games with DLCs: {len(dlc_dict)}")
- if want_entitlements:
- # Get entitlements, Ubisoft integration also uses them
- self.signals.progress.emit(40, self.signals.tr("Updating entitlements"))
- with timelogger(logger, "Request entitlements"):
- entitlements = self.core.egs.get_user_entitlements()
- self.core.lgd.entitlements = entitlements
- logger.info(f"Entitlements: {len(list(entitlements))}")
-
- self.signals.progress.emit(50, self.signals.tr("Preparing games"))
- self.signals.result.emit((games, dlc_dict), FetchWorker.Result.COMBINED)
+ self.signals.progress.emit(40, self.signals.tr("Preparing library"))
+ self.signals.result.emit((games, dlc_dict), FetchWorker.Result.GAMESDLCS)
From a605bddffaebc9f04c0deb7ff7e495e2ed72a968 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 19 Jan 2024 12:09:57 +0200
Subject: [PATCH 16/29] RareGame: Move `sdl_name` to RareGameBase
InstallDialog uses that property so RareEosOverlay should have it too.
Also calculate the base_path for the overlay case instead of passing it as
argument
---
rare/components/dialogs/install_dialog.py | 3 ++-
rare/models/base_game.py | 5 +++++
rare/models/game.py | 12 ++----------
3 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py
index 1f5828f7..a8f23c25 100644
--- a/rare/components/dialogs/install_dialog.py
+++ b/rare/components/dialogs/install_dialog.py
@@ -235,7 +235,8 @@ class InstallDialog(ActionDialog):
self.error_box()
def get_options(self):
- self.__options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text()
+ base_path = os.path.join(self.install_dir_edit.text(), ".overlay" if self.__options.overlay else "")
+ self.__options.base_path = "" if self.rgame.is_installed else base_path
self.__options.platform = self.ui.platform_combo.currentText()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
self.__options.max_workers = self.advanced.ui.max_workers_spin.value()
diff --git a/rare/models/base_game.py b/rare/models/base_game.py
index ab34b466..0033530a 100644
--- a/rare/models/base_game.py
+++ b/rare/models/base_game.py
@@ -8,6 +8,7 @@ from typing import Optional, List, Tuple
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, QThreadPool, QSettings
from legendary.lfs import eos
from legendary.models.game import SaveGameFile, SaveGameStatus, Game, InstalledGame
+from legendary.utils.selective_dl import get_sdl_appname
from rare.lgndr.core import LegendaryCore
from rare.models.install import UninstallOptionsModel, InstallOptionsModel
@@ -178,6 +179,10 @@ class RareGameBase(QObject):
except AttributeError:
return False
+ @property
+ def sdl_name(self) -> Optional[str]:
+ return get_sdl_appname(self.app_name)
+
@property
def version(self) -> str:
"""!
diff --git a/rare/models/game.py b/rare/models/game.py
index 0c305089..6c255a24 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -11,7 +11,6 @@ from PyQt5.QtCore import QRunnable, pyqtSlot, QProcess, QThreadPool
from PyQt5.QtGui import QPixmap
from legendary.lfs import eos
from legendary.models.game import Game, InstalledGame
-from legendary.utils.selective_dl import get_sdl_appname
from rare.lgndr.core import LegendaryCore
from rare.models.base_game import RareGameBase, RareGameSlim
@@ -413,10 +412,6 @@ class RareGame(RareGameSlim):
else self.app_title
)
- @property
- def sdl_name(self) -> Optional[str]:
- return get_sdl_appname(self.app_name)
-
@property
def save_path(self) -> Optional[str]:
return super(RareGame, self).save_path
@@ -628,7 +623,7 @@ class RareEosOverlay(RareGameBase):
try:
eos.add_registry_entries(path, prefix)
except PermissionError as e:
- logger.error("Exception while writing registry to enable the overlay .")
+ logger.error("Exception while writing registry to enable the overlay.")
logger.error(e)
return False
logger.info(f"Enabled overlay at: {path} for prefix: {prefix}")
@@ -652,10 +647,7 @@ class RareEosOverlay(RareGameBase):
if self.is_installed:
base_path = self.igame.install_path
else:
- base_path = os.path.join(
- self.core.lgd.config.get("Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")),
- ".overlay"
- )
+ base_path = self.core.get_default_install_dir()
self.signals.game.install.emit(
InstallOptionsModel(
app_name=self.app_name, base_path=base_path, platform="Windows", overlay=True
From 451017e2e21447f3b34b79defa095947a567bd74 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 19 Jan 2024 12:10:30 +0200
Subject: [PATCH 17/29] Ui: Fix and issue with legendary settings UI file
---
rare/ui/components/tabs/settings/legendary.py | 34 +++++++++----------
rare/ui/components/tabs/settings/legendary.ui | 4 +--
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/rare/ui/components/tabs/settings/legendary.py b/rare/ui/components/tabs/settings/legendary.py
index a8306397..8bdcc871 100644
--- a/rare/ui/components/tabs/settings/legendary.py
+++ b/rare/ui/components/tabs/settings/legendary.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/legendary.ui'
#
-# Created by: PyQt5 UI code generator 5.15.7
+# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_LegendarySettings(object):
def setupUi(self, LegendarySettings):
LegendarySettings.setObjectName("LegendarySettings")
- LegendarySettings.resize(681, 456)
+ LegendarySettings.resize(608, 420)
LegendarySettings.setWindowTitle("LegendarySettings")
self.legendary_layout = QtWidgets.QHBoxLayout(LegendarySettings)
self.legendary_layout.setObjectName("legendary_layout")
@@ -128,32 +128,32 @@ class Ui_LegendarySettings(object):
self.right_layout.addWidget(self.cleanup_group)
self.metadata_group = QtWidgets.QGroupBox(LegendarySettings)
self.metadata_group.setObjectName("metadata_group")
- self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.metadata_group)
- self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.metadata_layout = QtWidgets.QVBoxLayout(self.metadata_group)
+ self.metadata_layout.setObjectName("metadata_layout")
self.fetch_win32_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_win32_check.setObjectName("fetch_win32_check")
- self.verticalLayout_2.addWidget(self.fetch_win32_check)
+ self.metadata_layout.addWidget(self.fetch_win32_check)
self.fetch_macos_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_macos_check.setObjectName("fetch_macos_check")
- self.verticalLayout_2.addWidget(self.fetch_macos_check)
+ self.metadata_layout.addWidget(self.fetch_macos_check)
self.fetch_unreal_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_unreal_check.setObjectName("fetch_unreal_check")
- self.verticalLayout_2.addWidget(self.fetch_unreal_check)
- self.exclude_non_asset_check = QtWidgets.QCheckBox(self.meta_group)
+ self.metadata_layout.addWidget(self.fetch_unreal_check)
+ self.exclude_non_asset_check = QtWidgets.QCheckBox(self.metadata_group)
self.exclude_non_asset_check.setObjectName("exclude_non_asset_check")
- self.verticalLayout_2.addWidget(self.exclude_non_asset_check)
- self.exclude_entitlements_check = QtWidgets.QCheckBox(self.meta_group)
+ self.metadata_layout.addWidget(self.exclude_non_asset_check)
+ self.exclude_entitlements_check = QtWidgets.QCheckBox(self.metadata_group)
self.exclude_entitlements_check.setObjectName("exclude_entitlements_check")
- self.verticalLayout_2.addWidget(self.exclude_entitlements_check)
+ self.metadata_layout.addWidget(self.exclude_entitlements_check)
self.metadata_info = QtWidgets.QLabel(self.metadata_group)
font = QtGui.QFont()
font.setItalic(True)
self.metadata_info.setFont(font)
self.metadata_info.setObjectName("metadata_info")
- self.verticalLayout_2.addWidget(self.metadata_info)
+ self.metadata_layout.addWidget(self.metadata_info)
self.refresh_metadata_button = QtWidgets.QPushButton(self.metadata_group)
self.refresh_metadata_button.setObjectName("refresh_metadata_button")
- self.verticalLayout_2.addWidget(self.refresh_metadata_button)
+ self.metadata_layout.addWidget(self.refresh_metadata_button)
self.right_layout.addWidget(self.metadata_group)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.right_layout.addItem(spacerItem1)
@@ -182,12 +182,12 @@ class Ui_LegendarySettings(object):
self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games"))
self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine"))
self.exclude_non_asset_check.setToolTip(_translate("LegendarySettings", "Do not load metadata for non-asset games (i.e. Origin games) on start-up.\n"
- "\n"
- "Disabling this greatly improves start-up time, but some games might not be visible in your library."))
+"\n"
+"Disabling this greatly improves start-up time, but some games might not be visible in your library."))
self.exclude_non_asset_check.setText(_translate("LegendarySettings", "Exclude non-asset games"))
self.exclude_entitlements_check.setToolTip(_translate("LegendarySettings", "Do not load entitlement data (i.e game\'s date of purchase) on start-up.\n"
- "\n"
- "Disabling this greatly improves start-up time, but some library filters may not work."))
+"\n"
+"Disabling this greatly improves start-up time, but some library filters may not work."))
self.exclude_entitlements_check.setText(_translate("LegendarySettings", "Exclude entitlements"))
self.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply"))
self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata"))
diff --git a/rare/ui/components/tabs/settings/legendary.ui b/rare/ui/components/tabs/settings/legendary.ui
index 9f1a9c4c..2aab38d6 100644
--- a/rare/ui/components/tabs/settings/legendary.ui
+++ b/rare/ui/components/tabs/settings/legendary.ui
@@ -6,8 +6,8 @@
0
0
- 681
- 456
+ 608
+ 420
From 99d0bca5fcd478517a6c8b73628408eb3932ac52 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 19 Jan 2024 12:12:33 +0200
Subject: [PATCH 18/29] ConfigHelper: Extend with specialized methods for
environment variables and wine/proton prefixes
---
rare/utils/config_helper.py | 79 ++++++++++++++++++++++++++++++-------
1 file changed, 65 insertions(+), 14 deletions(-)
diff --git a/rare/utils/config_helper.py b/rare/utils/config_helper.py
index 31ef5973..66b7ac5e 100644
--- a/rare/utils/config_helper.py
+++ b/rare/utils/config_helper.py
@@ -20,26 +20,39 @@ def save_config():
_save_config()
-def add_option(app_name: str, option: str, value: str):
+def add_option(app_name: str, option: str, value: str) -> None:
value = value.replace("%%", "%").replace("%", "%%")
if not _config.has_section(app_name):
_config[app_name] = {}
-
_config.set(app_name, option, value)
save_config()
-def remove_option(app_name, option):
+def add_envvar(app_name: str, envvar: str, value: str) -> None:
+ add_option(f"{app_name}.env", envvar, value)
+
+
+def remove_option(app_name: str, option: str) -> None:
if _config.has_option(app_name, option):
_config.remove_option(app_name, option)
-
# if _config.has_section(app_name) and not _config[app_name]:
# _config.remove_section(app_name)
-
save_config()
-def remove_section(app_name):
+def remove_envvar(app_name: str, option: str) -> None:
+ remove_option(f"{app_name}.env", option)
+
+
+def get_option(app_name: str, option: str, fallback: Any = None) -> str:
+ return _config.get(app_name, option, fallback=fallback)
+
+
+def get_envvar(app_name: str, option: str, fallback: Any = None) -> str:
+ return get_option(f"{app_name}.env", option, fallback=fallback)
+
+
+def remove_section(app_name: str) -> None:
return
# Disabled due to env variables implementation
if _config.has_section(app_name):
@@ -61,13 +74,14 @@ def get_game_envvar(option: str, app_name: Optional[str] = None, fallback: Any =
return _option
+def get_proton_compat_data(app_name: Optional[str] = None, fallback: Any = None) -> str:
+ _compat = get_game_envvar("STEAM_COMPAT_DATA_PATH", app_name, fallback=fallback)
+ # return os.path.join(_compat, "pfx") if _compat else fallback
+ return _compat
+
+
def get_wine_prefix(app_name: Optional[str] = None, fallback: Any = None) -> str:
- _prefix = os.path.join(
- _config.get("default.env", "STEAM_COMPAT_DATA_PATH", fallback=fallback), "pfx")
- if app_name is not None:
- _prefix = os.path.join(
- _config.get(f'{app_name}.env', "STEAM_COMPAT_DATA_PATH", fallback=_prefix), "pfx")
- _prefix = _config.get("default.env", "WINEPREFIX", fallback=_prefix)
+ _prefix = _config.get("default.env", "WINEPREFIX", fallback=fallback)
_prefix = _config.get("default", "wine_prefix", fallback=_prefix)
if app_name is not None:
_prefix = _config.get(f'{app_name}.env', 'WINEPREFIX', fallback=_prefix)
@@ -79,10 +93,47 @@ def get_wine_prefixes() -> Set[str]:
_prefixes = []
for name, section in _config.items():
pfx = section.get("WINEPREFIX") or section.get("wine_prefix")
- if not pfx:
- pfx = os.path.join(compatdata, "pfx") if (compatdata := section.get("STEAM_COMPAT_DATA_PATH")) else ""
if pfx:
_prefixes.append(pfx)
_prefixes = [os.path.expanduser(prefix) for prefix in _prefixes]
return {p for p in _prefixes if os.path.isdir(p)}
+
+def get_proton_prefixes() -> Set[str]:
+ _prefixes = []
+ for name, section in _config.items():
+ pfx = os.path.join(compatdata, "pfx") if (compatdata := section.get("STEAM_COMPAT_DATA_PATH")) else ""
+ if pfx:
+ _prefixes.append(pfx)
+ _prefixes = [os.path.expanduser(prefix) for prefix in _prefixes]
+ return {p for p in _prefixes if os.path.isdir(p)}
+
+
+def get_prefixes() -> Set[str]:
+ return get_wine_prefixes().union(get_proton_prefixes())
+
+
+def prefix_exists(pfx: str) -> bool:
+ return os.path.isdir(pfx) and os.path.isfile(os.path.join(pfx, "user.reg"))
+
+
+def get_prefix(app_name: str = "default") -> Optional[str]:
+ _compat_path = _config.get(f"{app_name}.env", "STEAM_COMPAT_DATA_PATH", fallback=None)
+ if _compat_path and prefix_exists(_compat_prefix := os.path.join(_compat_path, "pfx")):
+ return _compat_prefix
+
+ _wine_prefix = _config.get(f"{app_name}.env", "WINEPREFIX", fallback=None)
+ _wine_prefix = _config.get(app_name, "wine_prefix", fallback=_wine_prefix)
+ if _wine_prefix and prefix_exists(_wine_prefix):
+ return _wine_prefix
+
+ _compat_path = _config.get(f"default.env", "STEAM_COMPAT_DATA_PATH", fallback=None)
+ if _compat_path and prefix_exists(_compat_prefix := os.path.join(_compat_path, "pfx")):
+ return _compat_prefix
+
+ _wine_prefix = _config.get(f"default.env", "WINEPREFIX", fallback=None)
+ _wine_prefix = _config.get("default", "wine_prefix", fallback=_wine_prefix)
+ if _wine_prefix and prefix_exists(_wine_prefix):
+ return _wine_prefix
+
+ return None
From 3c01cfc0a8f417155d64ffea3d81bd03577c4113 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 19 Jan 2024 14:54:09 +0200
Subject: [PATCH 19/29] EosOverlay: Fix a few remaining issues
* Don't create path when preparing overlay download, it fails on updates.
* Concatenate the overlay install path in InstallDialog instead of passing it as `base_path`
* Respect RareGame state in in the EOS overlay form
---
.../tabs/games/integrations/__init__.py | 4 +-
.../tabs/games/integrations/eos_group.py | 60 ++++++++++++++-----
rare/models/game.py | 12 ++--
rare/shared/workers/install_info.py | 3 -
4 files changed, 52 insertions(+), 27 deletions(-)
diff --git a/rare/components/tabs/games/integrations/__init__.py b/rare/components/tabs/games/integrations/__init__.py
index 0ecbc088..0e9eb6bb 100644
--- a/rare/components/tabs/games/integrations/__init__.py
+++ b/rare/components/tabs/games/integrations/__init__.py
@@ -1,7 +1,7 @@
from typing import Optional
from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLabel, QSpacerItem, QSizePolicy
+from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLabel, QSizePolicy
from rare.widgets.side_tab import SideTabWidget
from .egl_sync_group import EGLSyncGroup
@@ -34,8 +34,8 @@ class IntegrationsTabs(SideTabWidget):
self.tr(""),
self,
)
- self.ubisoft_group = UbisoftGroup(self.eos_ubisoft)
self.eos_group = EosGroup(self.eos_ubisoft)
+ self.ubisoft_group = UbisoftGroup(self.eos_ubisoft)
self.eos_ubisoft.addWidget(self.eos_group)
self.eos_ubisoft.addWidget(self.ubisoft_group)
self.eos_ubisoft_index = self.addTab(self.eos_ubisoft, self.tr("Epic Overlay and Ubisoft"))
diff --git a/rare/components/tabs/games/integrations/eos_group.py b/rare/components/tabs/games/integrations/eos_group.py
index 7cd8c163..2ed3bb0f 100644
--- a/rare/components/tabs/games/integrations/eos_group.py
+++ b/rare/components/tabs/games/integrations/eos_group.py
@@ -4,6 +4,7 @@ from logging import getLogger
from typing import Optional
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, Qt, pyqtSlot, QSize
+from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import (
QGroupBox,
QMessageBox,
@@ -20,7 +21,7 @@ from rare.lgndr.core import LegendaryCore
from rare.models.game import RareEosOverlay
from rare.shared import RareCore
from rare.ui.components.tabs.games.integrations.eos_widget import Ui_EosWidget
-from rare.utils import config_helper
+from rare.utils import config_helper as config
from rare.utils.misc import icon
from rare.widgets.elide_label import ElideLabel
@@ -51,7 +52,10 @@ class EosPrefixWidget(QFrame):
self.indicator = QLabel(parent=self)
self.indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
- self.prefix_label = ElideLabel(prefix if prefix is not None else overlay.app_title, parent=self)
+ self.prefix_label = ElideLabel(
+ prefix.replace(os.path.expanduser("~"), "~") if prefix is not None else overlay.app_title,
+ parent=self,
+ )
self.overlay_label = ElideLabel(parent=self)
self.overlay_label.setDisabled(True)
@@ -93,7 +97,7 @@ class EosPrefixWidget(QFrame):
def update_state(self) -> None:
active_path = os.path.normpath(p) if (p := self.overlay.active_path(self.prefix)) else ""
- self.overlay_label.setText(f"{active_path}<\i>")
+ self.overlay_label.setText(f"{active_path}")
self.path_select.clear()
if not self.overlay.is_installed and not self.overlay.available_paths(self.prefix):
@@ -103,8 +107,6 @@ class EosPrefixWidget(QFrame):
self.button.setText(self.tr("Unavailable"))
return
- self.setDisabled(False)
-
if self.overlay.is_enabled(self.prefix):
self.indicator.setPixmap(icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20)))
else:
@@ -120,6 +122,8 @@ class EosPrefixWidget(QFrame):
self.path_select.setItemData(self.path_select.findData(path), path, Qt.ToolTipRole)
self.path_select.setCurrentIndex(self.path_select.findData(active_path))
+ self.setEnabled(self.overlay.state == RareEosOverlay.State.IDLE)
+
@pyqtSlot()
def action(self) -> None:
path = self.path_select.currentData(Qt.UserRole)
@@ -128,10 +132,14 @@ class EosPrefixWidget(QFrame):
if self.overlay.is_enabled(self.prefix) and (path == active_path):
if not self.overlay.disable(prefix=self.prefix):
QMessageBox.warning(
- self, "Warning",
+ self,
+ "Warning",
self.tr("Failed to completely disable the active EOS Overlay.{}").format(
- self.tr(" Since the previous overlay was managed by EGL you can safely ignore this is.")
- if active_path != install_path else ""
+ self.tr(
+ " Since the previous overlay was managed by EGL you can safely ignore this is."
+ )
+ if active_path != install_path
+ else ""
),
)
else:
@@ -141,7 +149,9 @@ class EosPrefixWidget(QFrame):
self,
"Warning",
self.tr("Failed to completely enable EOS overlay.{}").format(
- self.tr(" Since the previous overlay was managed by EGL you can safely ignore this is.")
+ self.tr(
+ " Since the previous overlay was managed by EGL you can safely ignore this is."
+ )
if active_path != install_path
else ""
),
@@ -175,6 +185,7 @@ class EosGroup(QGroupBox):
self.signals = self.rcore.signals()
self.overlay = self.rcore.get_overlay()
+ self.overlay.signals.widget.update.connect(self.update_state)
self.overlay.signals.game.installed.connect(self.install_finished)
self.overlay.signals.game.uninstalled.connect(self.uninstall_finished)
@@ -191,18 +202,29 @@ class EosGroup(QGroupBox):
self.ui.update_button.setEnabled(False)
self.threadpool = QThreadPool.globalInstance()
+ self.worker: Optional[CheckForUpdateWorker] = None
- def showEvent(self, a0) -> None:
+ def showEvent(self, a0: QShowEvent) -> None:
+ if a0.spontaneous():
+ return super().showEvent(a0)
self.check_for_update()
self.update_prefixes()
+ self.update_state()
super().showEvent(a0)
+ @pyqtSlot()
+ def update_state(self):
+ self.ui.install_button.setEnabled(self.overlay.state == RareEosOverlay.State.IDLE)
+ self.ui.update_button.setEnabled(self.overlay.state == RareEosOverlay.State.IDLE and self.overlay.has_update)
+ self.ui.uninstall_button.setEnabled(self.overlay.state == RareEosOverlay.State.IDLE)
+
def update_prefixes(self):
for widget in self.findChildren(EosPrefixWidget, options=Qt.FindDirectChildrenOnly):
widget.deleteLater()
if platform.system() != "Windows":
- prefixes = config_helper.get_wine_prefixes()
+ prefixes = config.get_prefixes()
+ prefixes = {prefix for prefix in prefixes if config.prefix_exists(prefix)}
if platform.system() == "Darwin":
# TODO: add crossover support
pass
@@ -214,16 +236,22 @@ class EosGroup(QGroupBox):
widget = EosPrefixWidget(self.overlay, None)
self.ui.eos_layout.addWidget(widget)
+ @pyqtSlot(bool)
+ def check_for_update_finished(self, update_available: bool):
+ self.worker = None
+ self.ui.update_button.setEnabled(update_available)
+
def check_for_update(self):
+ self.ui.update_button.setEnabled(False)
if not self.overlay.is_installed:
return
- def worker_finished(update_available):
- self.ui.update_button.setEnabled(update_available)
+ if self.worker is not None:
+ return
- worker = CheckForUpdateWorker(self.core)
- worker.signals.update_available.connect(worker_finished)
- QThreadPool.globalInstance().start(worker)
+ self.worker = CheckForUpdateWorker(self.core)
+ self.worker.signals.update_available.connect(self.check_for_update_finished)
+ QThreadPool.globalInstance().start(self.worker)
@pyqtSlot()
def install_finished(self):
diff --git a/rare/models/game.py b/rare/models/game.py
index 6c255a24..70f6300f 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -575,7 +575,9 @@ class RareEosOverlay(RareGameBase):
@property
def has_update(self) -> bool:
- self.core.check_for_overlay_updates()
+ # lk: Don't check for updates here to ensure fast return
+ # There is already a thread in the EosGroup form to update it for us asynchronously
+ # and legendary does it too during login
return self.core.overlay_update_available
def is_enabled(self, prefix: Optional[str] = None) -> bool:
@@ -644,13 +646,11 @@ class RareEosOverlay(RareGameBase):
def install(self) -> bool:
if not self.is_idle:
return False
- if self.is_installed:
- base_path = self.igame.install_path
- else:
- base_path = self.core.get_default_install_dir()
self.signals.game.install.emit(
InstallOptionsModel(
- app_name=self.app_name, base_path=base_path, platform="Windows", overlay=True
+ app_name=self.app_name,
+ base_path=self.core.get_default_install_dir(),
+ platform="Windows", update=self.is_installed, overlay=True
)
)
return True
diff --git a/rare/shared/workers/install_info.py b/rare/shared/workers/install_info.py
index 1b5a921e..d4ff1877 100644
--- a/rare/shared/workers/install_info.py
+++ b/rare/shared/workers/install_info.py
@@ -41,9 +41,6 @@ class InstallInfoWorker(Worker):
else:
raise LgndrException(status.message)
else:
- if not os.path.exists(path := self.options.base_path):
- os.makedirs(path)
-
dlm, analysis, igame = self.core.prepare_overlay_install(
path=self.options.base_path
)
From 7aa64b385ef290fc58cc1414d133082d13921219 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Wed, 20 Sep 2023 01:32:12 +0300
Subject: [PATCH 20/29] Downloads: Move auto-update change to Rare's settings
instead of the game's metadata
The setting doesn't have a switch in the GUI yet, but the settings
feels like a better place for it.
---
rare/components/tabs/downloads/__init__.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index 85df8598..c9932f13 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -105,7 +105,9 @@ class DownloadsTab(QWidget):
def __add_update(self, update: Union[str, RareGame]):
if isinstance(update, str):
update = self.rcore.get_game(update)
- if update.metadata.auto_update or QSettings().value("auto_update", False, bool):
+ if QSettings().value(
+ f"{update.app_name}/auto_update", False, bool
+ ) or QSettings().value("auto_update", False, bool):
self.__get_install_options(
InstallOptionsModel(app_name=update.app_name, update=True, silent=True)
)
From 4e6008a8f7bc03d0d4f23a446a31f87252b04c10 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 19 Jan 2024 17:17:31 +0200
Subject: [PATCH 21/29] TrayIcon: Generic notifications
* Add a notification when starting a download too
---
rare/components/tabs/downloads/__init__.py | 18 +++++++++++++-----
rare/components/tabs/downloads/thread.py | 11 +++++------
rare/components/tray_icon.py | 13 +++----------
rare/models/signals.py | 4 ++--
4 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index c9932f13..c7bb2216 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -194,7 +194,7 @@ class DownloadsTab(QWidget):
if item.expired:
self.__refresh_download(item)
return
- dl_thread = DlThread(item, self.rcore.get_game(item.options.app_name), self.core, self.args.debug)
+ dl_thread = DlThread(item, rgame, self.core, self.args.debug)
dl_thread.result.connect(self.__on_download_result)
dl_thread.progress.connect(self.__on_download_progress)
dl_thread.finished.connect(dl_thread.deleteLater)
@@ -206,6 +206,11 @@ class DownloadsTab(QWidget):
RareCore.instance().image_manager().get_pixmap(rgame.app_name, True)
)
+ self.signals.application.notify.emit(
+ self.tr("Downloads"),
+ self.tr("Starting: \"{}\" is now downloading.").format(rgame.app_title)
+ )
+
@pyqtSlot(UIUpdate, object)
def __on_download_progress(self, ui_update: UIUpdate, dl_size: int):
self.download_widget.ui.progress_bar.setValue(int(ui_update.progress))
@@ -231,16 +236,19 @@ class DownloadsTab(QWidget):
if result.shortcut and desktop_links_supported():
if not create_desktop_link(
app_name=result.options.app_name,
- app_title=result.shortcut_title,
- link_name=result.shortcut_name,
+ app_title=result.app_title,
+ link_name=result.folder_name,
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()}")
else:
- logger.info(f"Created desktop link {result.shortcut_name} for {result.options.app_name}")
+ logger.info(f"Created desktop link {result.folder_name} for {result.app_title}")
- self.signals.application.notify.emit(result.options.app_name)
+ self.signals.application.notify.emit(
+ self.tr("Downloads"),
+ self.tr("Finished: \"{}\" is now playable.").format(result.app_title),
+ )
if self.updates_group.contains(result.options.app_name):
self.updates_group.set_widget_enabled(result.options.app_name, True)
diff --git a/rare/components/tabs/downloads/thread.py b/rare/components/tabs/downloads/thread.py
index 9309ef25..7aaa7486 100644
--- a/rare/components/tabs/downloads/thread.py
+++ b/rare/components/tabs/downloads/thread.py
@@ -34,8 +34,8 @@ class DlResultModel:
sync_saves: bool = False
tip_url: str = ""
shortcut: bool = False
- shortcut_name: str = ""
- shortcut_title: str = ""
+ folder_name: str = ""
+ app_title: str = ""
class DlThread(QThread):
@@ -151,10 +151,9 @@ class DlThread(QThread):
self.item.download.repair_file,
)
- if not self.item.options.update and self.item.options.create_shortcut:
- result.shortcut = True
- result.shortcut_name = self.rgame.folder_name
- result.shortcut_title = self.rgame.app_title
+ result.shortcut = not self.item.options.update and self.item.options.create_shortcut
+ result.folder_name = self.rgame.folder_name
+ result.app_title = self.rgame.app_title
self.__finish(result)
diff --git a/rare/components/tray_icon.py b/rare/components/tray_icon.py
index a0c49aa9..8b40cca1 100644
--- a/rare/components/tray_icon.py
+++ b/rare/components/tray_icon.py
@@ -59,17 +59,10 @@ class TrayIcon(QSystemTrayIcon):
last_played.sort(key=lambda g: g.metadata.last_played, reverse=True)
return last_played[0:5]
- @pyqtSlot(str)
- def notify(self, app_name: str):
+ @pyqtSlot(str, str)
+ def notify(self, title: str, body: str):
if self.settings.value("notification", True, bool):
- self.showMessage(
- self.tr("Download finished"),
- self.tr("Download finished. {} is playable now").format(
- self.rcore.get_game(app_name).app_title
- ),
- self.Information,
- 4000,
- )
+ self.showMessage(f"{QApplication.applicationName()} - {title}", body, QSystemTrayIcon.Information, 4000)
@pyqtSlot()
def update_actions(self):
diff --git a/rare/models/signals.py b/rare/models/signals.py
index 3c614237..522ad7f9 100644
--- a/rare/models/signals.py
+++ b/rare/models/signals.py
@@ -11,8 +11,8 @@ class GlobalSignals:
class ApplicationSignals(QObject):
# int: exit code
quit = pyqtSignal(int)
- # str: app_title
- notify = pyqtSignal(str)
+ # str: title, str: body
+ notify = pyqtSignal(str, str)
# none
prefix_updated = pyqtSignal()
# none
From 06e2c9b714cf2c53c407237b29256bb76299d438 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Fri, 19 Jan 2024 17:17:48 +0200
Subject: [PATCH 22/29] DebugSettings: Add button to test notifications
---
rare/components/tabs/settings/debug.py | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/rare/components/tabs/settings/debug.py b/rare/components/tabs/settings/debug.py
index 8c6b04b2..14aafd12 100644
--- a/rare/components/tabs/settings/debug.py
+++ b/rare/components/tabs/settings/debug.py
@@ -8,17 +8,23 @@ class DebugSettings(QWidget):
def __init__(self, parent=None):
super(DebugSettings, self).__init__(parent=parent)
- self.raise_runtime_exception_button = QPushButton("Raise Exception")
+ 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.restart_button = QPushButton("Restart", self)
self.restart_button.clicked.connect(
lambda: GlobalSignalsSingleton().application.quit.emit(ExitCodes.LOGOUT)
)
+ self.send_notification_button = QPushButton("Notify", self)
+ self.send_notification_button.clicked.connect(self.send_notification)
layout = QVBoxLayout(self)
layout.addWidget(self.raise_runtime_exception_button)
layout.addWidget(self.restart_button)
+ layout.addWidget(self.send_notification_button)
layout.addStretch(1)
def raise_exception(self):
raise RuntimeError("Debug Crash")
+
+ def send_notification(self):
+ GlobalSignalsSingleton().application.notify.emit("Debug", "Test notification")
From ce0b9788ee132737ae8228cb44175a483f855e62 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Thu, 18 Jan 2024 16:42:10 +0200
Subject: [PATCH 23/29] RareGame: Clear pixmap cache before loading a new
pixmap
---
rare/models/game.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/rare/models/game.py b/rare/models/game.py
index 70f6300f..cfc295e6 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -8,7 +8,7 @@ from threading import Lock
from typing import List, Optional, Dict, Set
from PyQt5.QtCore import QRunnable, pyqtSlot, QProcess, QThreadPool
-from PyQt5.QtGui import QPixmap
+from PyQt5.QtGui import QPixmap, QPixmapCache
from legendary.lfs import eos
from legendary.models.game import Game, InstalledGame
@@ -481,6 +481,7 @@ class RareGame(RareGameSlim):
def set_pixmap(self):
self.pixmap = self.image_manager.get_pixmap(self.app_name, self.is_installed)
+ QPixmapCache.clear()
if not self.pixmap.isNull():
self.signals.widget.update.emit()
From 7b52dc204ceb074d038ed3d4ed16d29f049abcb8 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sat, 20 Jan 2024 14:23:20 +0200
Subject: [PATCH 24/29] Downloads: Update queue count when adding a download
---
rare/components/tabs/downloads/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index c7bb2216..0145066f 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -326,6 +326,7 @@ class DownloadsTab(QWidget):
if self.updates_group.contains(item.options.app_name):
self.updates_group.set_widget_enabled(item.options.app_name, True)
rgame.state = RareGame.State.IDLE
+ self.update_queues_count()
@pyqtSlot(UninstallOptionsModel)
def __get_uninstall_options(self, options: UninstallOptionsModel):
From 5b367270766b93bff2fd38ed90246eec71f23671 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sat, 20 Jan 2024 16:45:44 +0200
Subject: [PATCH 25/29] Dialogs: Add subtitle label in ButtonDialog
---
rare/components/dialogs/install_dialog.py | 3 +-
rare/components/dialogs/move_dialog.py | 4 +-
rare/components/dialogs/selective_dialog.py | 10 +----
rare/components/dialogs/uninstall_dialog.py | 5 +--
rare/ui/components/dialogs/install_dialog.py | 36 ++++++++----------
rare/ui/components/dialogs/install_dialog.ui | 39 ++++++++------------
rare/widgets/dialogs.py | 20 ++++++++--
7 files changed, 54 insertions(+), 63 deletions(-)
diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py
index a8f23c25..cbe99018 100644
--- a/rare/components/dialogs/install_dialog.py
+++ b/rare/components/dialogs/install_dialog.py
@@ -75,13 +75,12 @@ class InstallDialog(ActionDialog):
header = self.tr("Modify")
bicon = icon("fa.gear")
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
+ self.setSubtitle(dialog_title_game(header, rgame.app_title))
install_widget = QWidget(self)
self.ui = Ui_InstallDialog()
self.ui.setupUi(install_widget)
- self.ui.title_label.setText(f"
{dialog_title_game(header, rgame.app_title)}
")
-
self.core = rgame.core
self.rgame = rgame
self.__options: InstallOptionsModel = options
diff --git a/rare/components/dialogs/move_dialog.py b/rare/components/dialogs/move_dialog.py
index cab68049..b81af267 100644
--- a/rare/components/dialogs/move_dialog.py
+++ b/rare/components/dialogs/move_dialog.py
@@ -34,8 +34,7 @@ class MoveDialog(ActionDialog):
super(MoveDialog, self).__init__(parent=parent)
header = self.tr("Move")
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
-
- title_label = QLabel(f"{dialog_title_game(header, rgame.app_title)}
", self)
+ self.setSubtitle(dialog_title_game(header, rgame.app_title))
self.rcore = RareCore.instance()
self.core = RareCore.instance().core()
@@ -70,7 +69,6 @@ class MoveDialog(ActionDialog):
layout = QVBoxLayout()
layout.setSizeConstraint(QLayout.SetFixedSize)
- layout.addWidget(title_label)
layout.addWidget(self.path_edit)
layout.addWidget(self.warn_label)
layout.addLayout(bottom_layout)
diff --git a/rare/components/dialogs/selective_dialog.py b/rare/components/dialogs/selective_dialog.py
index 1c3b5031..ac7409e2 100644
--- a/rare/components/dialogs/selective_dialog.py
+++ b/rare/components/dialogs/selective_dialog.py
@@ -15,8 +15,7 @@ class SelectiveDialog(ButtonDialog):
super(SelectiveDialog, self).__init__(parent=parent)
header = self.tr("Optional downloads for")
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
-
- title_label = QLabel(f"{dialog_title_game(header, rgame.app_title)}
", self)
+ self.setSubtitle(dialog_title_game(header, rgame.app_title))
self.rgame = rgame
self.selective_widget = SelectiveWidget(rgame, rgame.igame.platform, self)
@@ -26,12 +25,7 @@ class SelectiveDialog(ButtonDialog):
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.addWidget(self.selective_widget)
- layout = QVBoxLayout()
- layout.setSizeConstraint(QLayout.SetFixedSize)
- layout.addWidget(title_label)
- layout.addWidget(container)
-
- self.setCentralLayout(layout)
+ self.setCentralWidget(container)
self.accept_button.setText(self.tr("Verify"))
self.accept_button.setIcon(icon("fa.check"))
diff --git a/rare/components/dialogs/uninstall_dialog.py b/rare/components/dialogs/uninstall_dialog.py
index dd6be379..07befe14 100644
--- a/rare/components/dialogs/uninstall_dialog.py
+++ b/rare/components/dialogs/uninstall_dialog.py
@@ -1,6 +1,5 @@
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import (
- QLabel,
QVBoxLayout,
QCheckBox,
)
@@ -18,8 +17,7 @@ class UninstallDialog(ButtonDialog):
super(UninstallDialog, self).__init__(parent=parent)
header = self.tr("Uninstall")
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
-
- title_label = QLabel(f"{dialog_title_game(header, rgame.app_title)}
", self)
+ self.setSubtitle(dialog_title_game(header, rgame.app_title))
self.keep_files = QCheckBox(self.tr("Keep files"))
self.keep_files.setChecked(bool(options.keep_files))
@@ -34,7 +32,6 @@ class UninstallDialog(ButtonDialog):
self.keep_overlay_keys.setEnabled(rgame.is_overlay)
layout = QVBoxLayout()
- layout.addWidget(title_label)
layout.addWidget(self.keep_files)
layout.addWidget(self.keep_config)
layout.addWidget(self.keep_overlay_keys)
diff --git a/rare/ui/components/dialogs/install_dialog.py b/rare/ui/components/dialogs/install_dialog.py
index 8dc5c664..9e680570 100644
--- a/rare/ui/components/dialogs/install_dialog.py
+++ b/rare/ui/components/dialogs/install_dialog.py
@@ -14,20 +14,17 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_InstallDialog(object):
def setupUi(self, InstallDialog):
InstallDialog.setObjectName("InstallDialog")
- InstallDialog.resize(197, 216)
+ InstallDialog.resize(197, 195)
InstallDialog.setWindowTitle("InstallDialog")
self.main_layout = QtWidgets.QFormLayout(InstallDialog)
self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.main_layout.setObjectName("main_layout")
- self.title_label = QtWidgets.QLabel(InstallDialog)
- self.title_label.setObjectName("title_label")
- self.main_layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.title_label)
self.install_dir_label = QtWidgets.QLabel(InstallDialog)
self.install_dir_label.setObjectName("install_dir_label")
- self.main_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.install_dir_label)
+ self.main_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.install_dir_label)
self.platform_label = QtWidgets.QLabel(InstallDialog)
self.platform_label.setObjectName("platform_label")
- self.main_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.platform_label)
+ self.main_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.platform_label)
self.platform_combo = QtWidgets.QComboBox(InstallDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -35,43 +32,43 @@ class Ui_InstallDialog(object):
sizePolicy.setHeightForWidth(self.platform_combo.sizePolicy().hasHeightForWidth())
self.platform_combo.setSizePolicy(sizePolicy)
self.platform_combo.setObjectName("platform_combo")
- self.main_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.platform_combo)
+ self.main_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.platform_combo)
self.shortcut_label = QtWidgets.QLabel(InstallDialog)
self.shortcut_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.shortcut_label.setObjectName("shortcut_label")
- self.main_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.shortcut_label)
+ self.main_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.shortcut_label)
self.shortcut_check = QtWidgets.QCheckBox(InstallDialog)
self.shortcut_check.setText("")
self.shortcut_check.setObjectName("shortcut_check")
- self.main_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.shortcut_check)
+ self.main_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.shortcut_check)
self.selectable_layout = QtWidgets.QVBoxLayout()
self.selectable_layout.setObjectName("selectable_layout")
- self.main_layout.setLayout(4, QtWidgets.QFormLayout.SpanningRole, self.selectable_layout)
+ self.main_layout.setLayout(3, QtWidgets.QFormLayout.SpanningRole, self.selectable_layout)
self.advanced_layout = QtWidgets.QVBoxLayout()
self.advanced_layout.setObjectName("advanced_layout")
- self.main_layout.setLayout(5, QtWidgets.QFormLayout.SpanningRole, self.advanced_layout)
+ self.main_layout.setLayout(4, QtWidgets.QFormLayout.SpanningRole, self.advanced_layout)
self.download_size_label = QtWidgets.QLabel(InstallDialog)
self.download_size_label.setObjectName("download_size_label")
- self.main_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.download_size_label)
+ self.main_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.download_size_label)
self.download_size_text = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setItalic(True)
self.download_size_text.setFont(font)
self.download_size_text.setObjectName("download_size_text")
- self.main_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.download_size_text)
+ self.main_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.download_size_text)
self.install_size_label = QtWidgets.QLabel(InstallDialog)
self.install_size_label.setObjectName("install_size_label")
- self.main_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.install_size_label)
+ self.main_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.install_size_label)
self.install_size_text = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setItalic(True)
self.install_size_text.setFont(font)
self.install_size_text.setWordWrap(True)
self.install_size_text.setObjectName("install_size_text")
- self.main_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.install_size_text)
+ self.main_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.install_size_text)
self.avail_space_label = QtWidgets.QLabel(InstallDialog)
self.avail_space_label.setObjectName("avail_space_label")
- self.main_layout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.avail_space_label)
+ self.main_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.avail_space_label)
self.avail_space = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setBold(True)
@@ -79,10 +76,10 @@ class Ui_InstallDialog(object):
self.avail_space.setFont(font)
self.avail_space.setText("")
self.avail_space.setObjectName("avail_space")
- self.main_layout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.avail_space)
+ self.main_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.avail_space)
self.warning_label = QtWidgets.QLabel(InstallDialog)
self.warning_label.setObjectName("warning_label")
- self.main_layout.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.warning_label)
+ self.main_layout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.warning_label)
self.warning_text = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setItalic(True)
@@ -92,13 +89,12 @@ class Ui_InstallDialog(object):
self.warning_text.setWordWrap(True)
self.warning_text.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse)
self.warning_text.setObjectName("warning_text")
- self.main_layout.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.warning_text)
+ self.main_layout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.warning_text)
self.retranslateUi(InstallDialog)
def retranslateUi(self, InstallDialog):
_translate = QtCore.QCoreApplication.translate
- self.title_label.setText(_translate("InstallDialog", "error"))
self.install_dir_label.setText(_translate("InstallDialog", "Install folder"))
self.platform_label.setText(_translate("InstallDialog", "Platform"))
self.shortcut_label.setText(_translate("InstallDialog", "Create shortcut"))
diff --git a/rare/ui/components/dialogs/install_dialog.ui b/rare/ui/components/dialogs/install_dialog.ui
index d16cf4d4..6b00a183 100644
--- a/rare/ui/components/dialogs/install_dialog.ui
+++ b/rare/ui/components/dialogs/install_dialog.ui
@@ -7,7 +7,7 @@
0
0
197
- 216
+ 195
@@ -17,28 +17,21 @@
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
-
-
- error
-
-
-
- -
+
-
Install folder
- -
+
-
Platform
- -
+
-
@@ -48,7 +41,7 @@
- -
+
-
Create shortcut
@@ -58,27 +51,27 @@
- -
+
-
- -
+
-
- -
+
-
- -
+
-
Download size
- -
+
-
@@ -90,14 +83,14 @@
- -
+
-
Total install size
- -
+
-
@@ -112,14 +105,14 @@
- -
+
-
Available space
- -
+
-
@@ -132,14 +125,14 @@
- -
+
-
Warning
- -
+
-
diff --git a/rare/widgets/dialogs.py b/rare/widgets/dialogs.py
index a5a97db8..2c6839b4 100644
--- a/rare/widgets/dialogs.py
+++ b/rare/widgets/dialogs.py
@@ -11,7 +11,7 @@ from PyQt5.QtWidgets import (
QVBoxLayout,
QHBoxLayout,
QWidget,
- QLayout, QSpacerItem, QSizePolicy,
+ QLayout, QSpacerItem, QSizePolicy, QLabel,
)
from rare.utils.misc import icon
@@ -74,6 +74,9 @@ class ButtonDialog(BaseDialog):
def __init__(self, parent=None):
super(ButtonDialog, self).__init__(parent=parent)
+ self.subtitle_label = QLabel(self)
+ self.subtitle_label.setVisible(False)
+
self.reject_button = QPushButton(self)
self.reject_button.setText(self.tr("Cancel"))
self.reject_button.setIcon(icon("fa.remove"))
@@ -91,6 +94,7 @@ class ButtonDialog(BaseDialog):
self.button_layout.addWidget(self.accept_button)
self.main_layout = QVBoxLayout(self)
+ self.main_layout.addWidget(self.subtitle_label)
# lk: dirty way to set a minimum width with fixed size constraint
spacer = QSpacerItem(
480, self.main_layout.spacing(),
@@ -103,13 +107,23 @@ class ButtonDialog(BaseDialog):
def close(self):
raise RuntimeError(f"Don't use `close()` with {type(self).__name__}")
+ def setSubtitle(self, text: str):
+ self.subtitle_label.setText(f"{text}")
+ self.subtitle_label.setVisible(True)
+
def setCentralWidget(self, widget: QWidget):
widget.layout().setContentsMargins(0, 0, 0, 0)
- self.main_layout.insertWidget(0, widget)
+ self.main_layout.insertWidget(
+ self.main_layout.indexOf(self.subtitle_label) + 1,
+ widget
+ )
def setCentralLayout(self, layout: QLayout):
layout.setContentsMargins(0, 0, 0, 0)
- self.main_layout.insertLayout(0, layout)
+ self.main_layout.insertLayout(
+ self.main_layout.indexOf(self.subtitle_label) + 1,
+ layout
+ )
@abstractmethod
def accept_handler(self):
From fa9b49c0194afa575e6c07345099214cb3f9ac86 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sat, 20 Jan 2024 16:57:28 +0200
Subject: [PATCH 26/29] SelectiveDialog: Keep the layout of the central widget
---
rare/components/dialogs/selective_dialog.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/rare/components/dialogs/selective_dialog.py b/rare/components/dialogs/selective_dialog.py
index ac7409e2..26b50968 100644
--- a/rare/components/dialogs/selective_dialog.py
+++ b/rare/components/dialogs/selective_dialog.py
@@ -25,7 +25,10 @@ class SelectiveDialog(ButtonDialog):
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.addWidget(self.selective_widget)
- self.setCentralWidget(container)
+ layout = QVBoxLayout()
+ layout.addWidget(container)
+
+ self.setCentralLayout(layout)
self.accept_button.setText(self.tr("Verify"))
self.accept_button.setIcon(icon("fa.check"))
From 69aca3851fb5e2040dd91a4b2ac96a4bbdc73e2a Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sun, 21 Jan 2024 23:42:38 +0200
Subject: [PATCH 27/29] InstallDialog: Update widget attribute name
---
rare/components/dialogs/install_dialog.py | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py
index cbe99018..6fafcae7 100644
--- a/rare/components/dialogs/install_dialog.py
+++ b/rare/components/dialogs/install_dialog.py
@@ -27,10 +27,10 @@ class InstallDialogAdvanced(CollapsibleFrame):
title = self.tr("Advanced options")
self.setTitle(title)
- widget = QWidget(parent=self)
+ self.widget = QWidget(parent=self)
self.ui = Ui_InstallDialogAdvanced()
- self.ui.setupUi(widget)
- self.setWidget(widget)
+ self.ui.setupUi(self.widget)
+ self.setWidget(self.widget)
class InstallDialogSelective(CollapsibleFrame):
@@ -42,18 +42,18 @@ class InstallDialogSelective(CollapsibleFrame):
self.setTitle(title)
self.setEnabled(bool(rgame.sdl_name))
- self.selective_widget: SelectiveWidget = None
+ self.widget: SelectiveWidget = None
self.rgame = rgame
def update_list(self, platform: str):
- if self.selective_widget is not None:
- self.selective_widget.deleteLater()
- self.selective_widget = SelectiveWidget(self.rgame, platform, parent=self)
- self.selective_widget.stateChanged.connect(self.stateChanged)
- self.setWidget(self.selective_widget)
+ if self.widget is not None:
+ self.widget.deleteLater()
+ self.widget = SelectiveWidget(self.rgame, platform, parent=self)
+ self.widget.stateChanged.connect(self.stateChanged)
+ self.setWidget(self.widget)
def install_tags(self):
- return self.selective_widget.install_tags()
+ return self.widget.install_tags()
class InstallDialog(ActionDialog):
From d9bfdb91ce26892699b1d409dbdac6f0995810ec Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Mon, 22 Jan 2024 00:16:40 +0200
Subject: [PATCH 28/29] Runners: Import vdf only on Linux and FreeBSD
---
rare/utils/proton.py | 4 +++-
requirements.txt | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/rare/utils/proton.py b/rare/utils/proton.py
index c0e3fb60..c54e6d42 100644
--- a/rare/utils/proton.py
+++ b/rare/utils/proton.py
@@ -1,9 +1,11 @@
+import platform as pf
import os
from dataclasses import dataclass
from logging import getLogger
from typing import Optional, Union, List, Dict
-import vdf
+if pf.system() in {"Linux", "FreeBSD"}:
+ import vdf
logger = getLogger("Proton")
diff --git a/requirements.txt b/requirements.txt
index 6d4303ef..4a9abb8f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,5 +5,5 @@ setuptools
legendary-gl>=0.20.34; platform_system != "Windows" or platform_system != "Darwin"
legendary-gl @ git+https://github.com/derrod/legendary@96e07ff ; platform_system == "Windows" or platform_system == "Darwin"
orjson
-vdf; platform_system != "Windows"
+vdf; platform_system == "Linux" or platform_system == "FreeBSD"
pywin32; platform_system == "Windows"
From 17246f1201aea5df79455aa18e35413cd4ca3523 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Mon, 22 Jan 2024 00:21:40 +0200
Subject: [PATCH 29/29] Workflows: Run pylint on a matrix of OSes and versions
---
.github/workflows/checks.yml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 3fa9536f..763e7be9 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -22,13 +22,17 @@ on:
jobs:
pylint:
- runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ os: [macos-latest, windows-latest, ubuntu-latest]
+ version: [3.9, 3.10, 3.11, 3.12]
+ runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
- python-version: '3.9'
+ python-version: ${{ matris.version }}
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip