From dda179389031cabcf0910c7039beff7e6f17dcf7 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:36:57 +0200 Subject: [PATCH 01/34] Rare: Version 1.10.8 --- AppImageBuilder.yml | 2 +- pyproject.toml | 2 +- rare/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index dc20b8e1..dcb6d67b 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -20,7 +20,7 @@ AppDir: id: io.github.dummerle.rare name: Rare icon: Rare - version: 1.10.7 + version: 1.10.8 exec: usr/bin/python3 exec_args: $APPDIR/usr/src/rare/main.py $@ apt: diff --git a/pyproject.toml b/pyproject.toml index 5d51e960..c21e48a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ force-exclude = ''' [tool.poetry] name = "rare" -version = "1.10.7" +version = "1.10.8" description = "A GUI for Legendary" authors = ["Dummerle"] license = "GPL3" diff --git a/rare/__init__.py b/rare/__init__.py index a1e30886..3de296ba 100644 --- a/rare/__init__.py +++ b/rare/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.10.7" +__version__ = "1.10.8" __codename__ = "Garlic Crab" # For PyCharm profiler From 23716e40d88df190e5d71635f4daec9ef63ac226 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:53:17 +0200 Subject: [PATCH 02/34] Lgndr: Log function name using the decorator --- rare/lgndr/cli.py | 4 ++-- rare/lgndr/core.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rare/lgndr/cli.py b/rare/lgndr/cli.py index 9261bcb8..d0a4e8d2 100644 --- a/rare/lgndr/cli.py +++ b/rare/lgndr/cli.py @@ -43,9 +43,9 @@ class LegendaryCLI(LegendaryCLIReal): def unlock_installed(func): @functools.wraps(func) def unlock(self, *args, **kwargs): - self.logger.debug("Using unlock decorator") + self.logger.debug("%s: Using unlock decorator", func.__name__) if not self.core.lgd.lock_installed(): - self.logger.info("Data is locked, trying to forcufully release it") + self.logger.info("Data is locked, trying to forcefully release it") # self.core.lgd._installed_lock.release(force=True) try: ret = func(self, *args, **kwargs) diff --git a/rare/lgndr/core.py b/rare/lgndr/core.py index aee89a78..478d3128 100644 --- a/rare/lgndr/core.py +++ b/rare/lgndr/core.py @@ -37,9 +37,9 @@ class LegendaryCore(LegendaryCoreReal): def unlock_installed(func): @functools.wraps(func) def unlock(self, *args, **kwargs): - self.log.debug("Using unlock decorator") + self.log.debug("%s: Using unlock decorator", func.__name__) if not self.lgd.lock_installed(): - self.log.info("Data is locked, trying to forcufully release it") + self.log.info("Data is locked, trying to forcefully release it") # self.lgd._installed_lock.release(force=True) try: ret = func(self, *args, **kwargs) From 5abae7ee16d39aad6caa2b94abbee660327a8e0e Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:49:22 +0200 Subject: [PATCH 03/34] FlowLayout: Fix overlapping widgets and remove default arguments --- rare/components/tabs/games/__init__.py | 1 + rare/widgets/flow_layout.py | 71 ++++++++++++++++++-------- rare/widgets/library_layout.py | 19 +++---- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index c206b4e3..3879da74 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -69,6 +69,7 @@ class GamesTab(QStackedWidget): self.icon_view = QWidget(self.icon_view_scroll) icon_view_layout = LibraryLayout(self.icon_view) + icon_view_layout.setSpacing(9) icon_view_layout.setContentsMargins(0, 13, 0, 13) icon_view_layout.setAlignment(Qt.AlignTop) diff --git a/rare/widgets/flow_layout.py b/rare/widgets/flow_layout.py index f8d32735..c13b67b5 100644 --- a/rare/widgets/flow_layout.py +++ b/rare/widgets/flow_layout.py @@ -1,33 +1,55 @@ -from typing import Optional +from typing import Optional, List, overload -from PyQt5.QtCore import ( - Qt, - QRect, - QSize, - QPoint, -) -from PyQt5.QtWidgets import ( - QLayout, - QStyle, - QSizePolicy, - QLayoutItem, -) +from PyQt5.QtCore import Qt, QRect, QSize, QPoint +from PyQt5.QtWidgets import QLayout, QStyle, QSizePolicy, QLayoutItem, QWidget class FlowLayout(QLayout): - def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1): + def __init__(self, parent=None): super(FlowLayout, self).__init__(parent) - self._hspacing = hspacing - self._vspacing = vspacing - self._items = [] - self.setContentsMargins(margin, margin, margin, margin) self.setObjectName(type(self).__name__) + self._hspacing = -1 + self._vspacing = -1 + self._items: List[QLayoutItem] = [] def __del__(self): del self._items[:] + @overload + def indexOf(self, a0: QWidget) -> int: + try: + return next(idx for idx, item in enumerate(self._items) if item.widget() is a0) + except: + return -1 + + def indexOf(self, a0: QLayoutItem) -> int: + try: + return self._items.index(a0) + except: + return -1 + def addItem(self, a0: QLayoutItem) -> None: self._items.append(a0) + self.invalidate() + + def removeItem(self, a0: QLayoutItem) -> None: + self._items.remove(a0) + self.invalidate() + + def spacing(self) -> int: + hspacing = self.horizontalSpacing() + if hspacing == self.verticalSpacing(): + return hspacing + else: + return -1 + + def setSpacing(self, a0: int) -> None: + self._hspacing = self._vspacing = a0 + self.invalidate() + + def setHorizontalSpacing(self, a0: int) -> None: + self._hspacing = a0 + self.invalidate() def horizontalSpacing(self): if self._hspacing >= 0: @@ -35,6 +57,10 @@ class FlowLayout(QLayout): else: return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing) + def setVerticalSpacing(self, a0: int) -> None: + self._vspacing = a0 + self.invalidate() + def verticalSpacing(self): if self._vspacing >= 0: return self._vspacing @@ -51,11 +77,14 @@ class FlowLayout(QLayout): def takeAt(self, index: int) -> Optional[QLayoutItem]: if 0 <= index < len(self._items): - return self._items.pop(index) + item = self._items.pop(index) + self.invalidate() + return item return None def expandingDirections(self) -> Qt.Orientations: - return Qt.Horizontal | Qt.Vertical + return Qt.Orientations(Qt.Orientation(0)) + # return Qt.Horizontal | Qt.Vertical def hasHeightForWidth(self) -> bool: return True @@ -93,8 +122,6 @@ class FlowLayout(QLayout): if item.isEmpty(): continue widget = item.widget() - if not widget.isVisible(): - continue hspace = self.horizontalSpacing() if hspace == -1: hspace = widget.style().layoutSpacing( diff --git a/rare/widgets/library_layout.py b/rare/widgets/library_layout.py index dd2c056d..721e1ad3 100644 --- a/rare/widgets/library_layout.py +++ b/rare/widgets/library_layout.py @@ -1,23 +1,18 @@ from typing import Callable -from PyQt5.QtCore import ( - Qt, - QRect, - QPoint, -) -from PyQt5.QtWidgets import ( - QSizePolicy, -) +from PyQt5.QtCore import Qt, QRect, QPoint +from PyQt5.QtWidgets import QSizePolicy from .flow_layout import FlowLayout class LibraryLayout(FlowLayout): - def __init__(self, parent=None, margin=6, spacing=11): - super(LibraryLayout, self).__init__(parent=parent, margin=margin, hspacing=spacing, vspacing=spacing) + def __init__(self, parent=None): + super(LibraryLayout, self).__init__(parent) def expandingDirections(self) -> Qt.Orientations: - return Qt.Horizontal | Qt.Vertical + return Qt.Orientations(Qt.Orientation(0)) + # return Qt.Horizontal | Qt.Vertical def setGeometry(self, a0: QRect) -> None: super(FlowLayout, self).setGeometry(a0) @@ -127,4 +122,4 @@ class LibraryLayout(FlowLayout): def sort(self, key: Callable, reverse=False) -> None: self._items.sort(key=key, reverse=reverse) - self.setGeometry(self.parent().contentsRect()) + self.setGeometry(self.parent().contentsRect().adjusted(*self.parent().getContentsMargins())) From cfbb6ea57864f8cb51f5f5f60d955e9cde9091d9 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:43:59 +0200 Subject: [PATCH 04/34] GamesTab: Initialize when first shown. --- rare/components/tabs/games/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index 3879da74..8b7044de 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -1,6 +1,7 @@ from logging import getLogger from PyQt5.QtCore import QSettings, Qt, pyqtSlot +from PyQt5.QtGui import QShowEvent from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame from rare.models.game import RareGame @@ -107,7 +108,14 @@ class GamesTab(QStackedWidget): self.signals.game.installed.connect(self.update_count_games_label) self.signals.game.uninstalled.connect(self.update_count_games_label) + self.init = True + + def showEvent(self, a0: QShowEvent): + if a0.spontaneous() or not self.init: + return super().showEvent(a0) self.setup_game_list() + self.init = False + return super().showEvent(a0) @pyqtSlot() def scroll_to_top(self): From dfa12a0138e90b9e7af4ff87a79e4f6d7fcec638 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:47:03 +0200 Subject: [PATCH 05/34] IconGameWidget: Make icons slightly smaller by using the Library preset Previously the Display and Library presets were equal. Make the icons smaller by making the Library preset ~75% of the Display.preset --- rare/components/tabs/games/game_widgets/icon_game_widget.py | 2 +- rare/models/image.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rare/components/tabs/games/game_widgets/icon_game_widget.py b/rare/components/tabs/games/game_widgets/icon_game_widget.py index bf75c4e2..0bdaf07e 100644 --- a/rare/components/tabs/games/game_widgets/icon_game_widget.py +++ b/rare/components/tabs/games/game_widgets/icon_game_widget.py @@ -15,7 +15,7 @@ class IconGameWidget(GameWidget): def __init__(self, rgame: RareGame, parent=None): super().__init__(rgame, parent) self.setObjectName(f"{rgame.app_name}") - self.setFixedSize(ImageSize.Display) + self.setFixedSize(ImageSize.Library) self.ui = IconWidget() self.ui.setupUi(self) diff --git a/rare/models/image.py b/rare/models/image.py index 6202f5d6..b32fa747 100644 --- a/rare/models/image.py +++ b/rare/models/image.py @@ -70,15 +70,15 @@ class ImageSize: DisplayWide = Preset(1, 1, Orientation.Wide, base=ImageWide) """! @brief Size and pixel ratio for wide 16/9 image display""" - Wide = DisplayWide + LibraryWide = Preset(1.33, 1, Orientation.Wide, base=ImageWide) - Normal = Display + Library = Preset(1.33, 1, base=Image) """! @brief Same as Display""" Small = Preset(3, 1, base=Image) """! @brief Small image size for displaying""" - SmallWide = Preset(1, 1, Orientation.Wide, base=ImageWide) + SmallWide = Preset(3, 1, Orientation.Wide, base=ImageWide) """! @brief Small image size for displaying""" Smaller = Preset(4, 1, base=Image) From 687218d29ba7b3de6a938ddd73887d87152d9f59 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 14 Dec 2023 23:08:08 +0200 Subject: [PATCH 06/34] RareGame: Return values only from Game for `app_name` and `app_title` With launchable DLCs we will need to replace the InstalledGame in the DLC with the InstalledGame from the base game so depend these properties solely on the Game attribute. Furthermore, since we do not need backwards compatibility any more, remove the `title` property and rename its uses to use `app_title` --- rare/components/tabs/downloads/__init__.py | 2 +- .../tabs/games/game_info/cloud_saves.py | 6 ++-- .../tabs/games/game_info/game_info.py | 28 +++++++++---------- rare/models/base_game.py | 16 ++++------- rare/shared/workers/wine_resolver.py | 10 +++---- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index 7017ebc3..dfff5865 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -346,5 +346,5 @@ class DownloadsTab(QWidget): @pyqtSlot(RareGame, bool, str) def __on_uninstall_worker_result(self, rgame: RareGame, success: bool, message: str): if not success: - QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.title), message, QMessageBox.Close) + QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.app_title), message, QMessageBox.Close) rgame.state = RareGame.State.IDLE diff --git a/rare/components/tabs/games/game_info/cloud_saves.py b/rare/components/tabs/games/game_info/cloud_saves.py index a39d492b..d9cd9f23 100644 --- a/rare/components/tabs/games/game_info/cloud_saves.py +++ b/rare/components/tabs/games/game_info/cloud_saves.py @@ -144,10 +144,10 @@ class CloudSaves(QWidget, SideTabContents): self.cloud_save_path_edit.setText("") QMessageBox.warning( self, - self.tr("Error - {}").format(self.rgame.title), + self.tr("Error - {}").format(self.rgame.app_title), self.tr( "Error while calculating path for {}. Insufficient permissions to create {}" - ).format(self.rgame.title, path), + ).format(self.rgame.app_title, path), ) return if not path: @@ -217,7 +217,7 @@ class CloudSaves(QWidget, SideTabContents): self.rgame = rgame - self.set_title.emit(rgame.title) + self.set_title.emit(rgame.app_title) rgame.signals.widget.update.connect(self.__update_widget) self.__update_widget() diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index a6735235..0fd1d7d4 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -121,7 +121,7 @@ class GameInfo(QWidget, SideTabContents): if not os.path.exists(repair_file): QMessageBox.warning( self, - self.tr("Error - {}").format(self.rgame.title), + self.tr("Error - {}").format(self.rgame.app_title), self.tr( "Repair file does not exist or game does not need a repair. Please verify game first" ), @@ -135,11 +135,11 @@ class GameInfo(QWidget, SideTabContents): if rgame.has_update: ans = QMessageBox.question( self, - self.tr("Repair and update? - {}").format(self.rgame.title), + self.tr("Repair and update? - {}").format(self.rgame.app_title), self.tr( "There is an update for {} from {} to {}. " "Do you want to update the game while repairing it?" - ).format(rgame.title, rgame.version, rgame.remote_version), + ).format(rgame.app_title, rgame.version, rgame.remote_version), ) == QMessageBox.Yes rgame.repair(repair_and_update=ans) @@ -147,7 +147,7 @@ class GameInfo(QWidget, SideTabContents): def __on_worker_error(self, rgame: RareGame, message: str): QMessageBox.warning( self, - self.tr("Error - {}").format(rgame.title), + self.tr("Error - {}").format(rgame.app_title), message ) @@ -155,11 +155,11 @@ class GameInfo(QWidget, SideTabContents): def __on_verify(self): """ This method is to be called from the button only """ if not os.path.exists(self.rgame.igame.install_path): - logger.error(f"Installation path {self.rgame.igame.install_path} for {self.rgame.title} does not exist") + logger.error(f"Installation path {self.rgame.igame.install_path} for {self.rgame.app_title} does not exist") QMessageBox.warning( self, - self.tr("Error - {}").format(self.rgame.title), - self.tr("Installation path for {} does not exist. Cannot continue.").format(self.rgame.title), + self.tr("Error - {}").format(self.rgame.app_title), + self.tr("Installation path for {} does not exist. Cannot continue.").format(self.rgame.app_title), ) return self.verify_game(self.rgame) @@ -184,18 +184,18 @@ class GameInfo(QWidget, SideTabContents): if success: QMessageBox.information( self, - self.tr("Summary - {}").format(rgame.title), + self.tr("Summary - {}").format(rgame.app_title), self.tr("{} has been verified successfully. " - "No missing or corrupt files found").format(rgame.title), + "No missing or corrupt files found").format(rgame.app_title), ) else: ans = QMessageBox.question( self, - self.tr("Summary - {}").format(rgame.title), + self.tr("Summary - {}").format(rgame.app_title), self.tr( "{} failed verification, {} file(s) corrupted, {} file(s) are missing. " "Do you want to repair them?" - ).format(rgame.title, failed, missing), + ).format(rgame.app_title, failed, missing), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes, ) @@ -216,7 +216,7 @@ class GameInfo(QWidget, SideTabContents): if os.path.basename(self.rgame.install_path) in os.path.basename(item): ans = QMessageBox.question( self, - self.tr("Move game? - {}").format(self.rgame.title), + self.tr("Move game? - {}").format(self.rgame.app_title), self.tr( "Destination {} already exists. " "Are you sure you want to overwrite it?" @@ -255,8 +255,8 @@ class GameInfo(QWidget, SideTabContents): def __on_move_result(self, rgame: RareGame, dst_path: str): QMessageBox.information( self, - self.tr("Summary - {}").format(rgame.title), - self.tr("{} successfully moved to {}.").format(rgame.title, dst_path), + self.tr("Summary - {}").format(rgame.app_title), + self.tr("{} successfully moved to {}.").format(rgame.app_title, dst_path), ) @pyqtSlot() diff --git a/rare/models/base_game.py b/rare/models/base_game.py index 8137fcf5..b3eff1bf 100644 --- a/rare/models/base_game.py +++ b/rare/models/base_game.py @@ -97,15 +97,11 @@ class RareGameBase(QObject): @property def app_name(self) -> str: - return self.igame.app_name if self.igame is not None else self.game.app_name + return self.game.app_name @property def app_title(self) -> str: - return self.igame.title if self.igame is not None else self.game.app_title - - @property - def title(self) -> str: - return self.app_title + return self.game.app_title @property @abstractmethod @@ -234,7 +230,7 @@ class RareGameSlim(RareGameBase): status, (dt_local, dt_remote) = self.save_game_state def _upload(): - logger.info(f"Uploading save for {self.title}") + logger.info(f"Uploading save for {self.app_title}") self.state = RareGameSlim.State.SYNCING self.core.upload_save(self.app_name, self.igame.save_path, dt_local) self.state = RareGameSlim.State.IDLE @@ -246,7 +242,7 @@ class RareGameSlim(RareGameBase): logger.warning("Can't upload non existing save") return if self.state == RareGameSlim.State.SYNCING: - logger.error(f"{self.title} is already syncing") + logger.error(f"{self.app_title} is already syncing") return if thread: @@ -259,7 +255,7 @@ class RareGameSlim(RareGameBase): status, (dt_local, dt_remote) = self.save_game_state def _download(): - logger.info(f"Downloading save for {self.title}") + logger.info(f"Downloading save for {self.app_title}") self.state = RareGameSlim.State.SYNCING self.core.download_saves(self.app_name, self.latest_save.file.manifest_name, self.save_path) self.state = RareGameSlim.State.IDLE @@ -271,7 +267,7 @@ class RareGameSlim(RareGameBase): logger.error("Can't download non existing save") return if self.state == RareGameSlim.State.SYNCING: - logger.error(f"{self.title} is already syncing") + logger.error(f"{self.app_title} is already syncing") return if thread: diff --git a/rare/shared/workers/wine_resolver.py b/rare/shared/workers/wine_resolver.py index 563e78b3..fe63ea4e 100644 --- a/rare/shared/workers/wine_resolver.py +++ b/rare/shared/workers/wine_resolver.py @@ -104,17 +104,17 @@ class OriginWineWorker(QRunnable): if install_dir: logger.debug("Found Unix install directory %s", install_dir) else: - logger.info("Could not find Unix install directory for %s", rgame.title) + logger.info("Could not find Unix install directory for %s", rgame.app_title) else: - logger.info("Could not find Wine install directory for %s", rgame.title) + logger.info("Could not find Wine install directory for %s", rgame.app_title) if install_dir: if os.path.isdir(install_dir): install_size = path_size(install_dir) rgame.set_origin_attributes(install_dir, install_size) - logger.info("Origin game %s (%s, %s)", rgame.title, install_dir, format_size(install_size)) + logger.info("Origin game %s (%s, %s)", rgame.app_title, install_dir, format_size(install_size)) else: - logger.warning("Origin game %s (%s does not exist)", rgame.title, install_dir) + logger.warning("Origin game %s (%s does not exist)", rgame.app_title, install_dir) else: - logger.info("Origin game %s is not installed", rgame.title) + logger.info("Origin game %s is not installed", rgame.app_title) logger.info("Origin worker finished in %ss", time.time() - t) From 1677ea762c279b551048772e0b55a115c061341d Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:57:32 +0200 Subject: [PATCH 07/34] FetchWorker: Fix issue with missing MacOS assets on MacOS Using `LegendaryCore.get_game_and_dlc_list` with platform `Windows` updated the assets only for the `Windows` builds of the games missing `Win32` and `MacOS` assets on clean installs. This caused Rare to not include MacOS install options on MacOS (duh!). This might also have been the cause that users were unable to launch games, since they where only offered the `Windows` build of the games (big duh!). To fix this, fetch the assets for `Win32` and `MacOS` games before getting the final list of games and dlcs based on the `Windows` platform. In this regard, also re-use the existing options for getting metadata to give the option to the user to include them when updating assets. Also add an option to include Unreal engine assets which until now were fetched unconditionally. * Include Unreal: When the user option is `true` or debugging. Defaults to `false` * Update Win32: When the user option is `true` or debugging. Defaults to `false` * Update MacOS: Force on MacOS, when the option is `true` or debugging on other platforms. Defaults to `true` on MacOS and is disabled, `false` on others Furthermore, respect legendary's `default_platform` config option and set it in the config on new configurations. The new method in our LegendaryCore monkey allows us to use that option in RareGame when doing version checks on not installed games, and not defaulting to `Windows`. Finally, set `install_platform_fallback` to false in a new config to avoid unwanted side-effects. --- rare/components/__init__.py | 2 - .../tabs/games/game_info/game_info.py | 4 +- .../tabs/games/integrations/ubisoft_group.py | 1 - rare/components/tabs/settings/legendary.py | 57 +++++++++++++------ rare/lgndr/core.py | 8 ++- rare/models/game.py | 5 +- rare/shared/rare_core.py | 10 +++- rare/shared/workers/fetch.py | 44 ++++++++++++-- rare/ui/components/tabs/settings/legendary.py | 51 ++++++++++------- rare/ui/components/tabs/settings/legendary.ui | 41 +++++++++---- 10 files changed, 162 insertions(+), 61 deletions(-) diff --git a/rare/components/__init__.py b/rare/components/__init__.py index 8d0e4fd4..684a1133 100644 --- a/rare/components/__init__.py +++ b/rare/components/__init__.py @@ -45,8 +45,6 @@ class Rare(RareApp): self.signals = RareCore.instance().signals() self.core = RareCore.instance().core() - config_helper.init_config_handler(self.core) - lang = self.settings.value("language", self.core.language_code, type=str) self.load_translator(lang) diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 0fd1d7d4..44e5b691 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -284,7 +284,9 @@ class GameInfo(QWidget, SideTabContents): ) self.ui.platform.setText( - self.rgame.igame.platform if self.rgame.is_installed and not self.rgame.is_non_asset else "Windows" + self.rgame.igame.platform + if self.rgame.is_installed and not self.rgame.is_non_asset + else self.core.default_platform ) self.ui.lbl_grade.setDisabled( diff --git a/rare/components/tabs/games/integrations/ubisoft_group.py b/rare/components/tabs/games/integrations/ubisoft_group.py index eddd303d..008912be 100644 --- a/rare/components/tabs/games/integrations/ubisoft_group.py +++ b/rare/components/tabs/games/integrations/ubisoft_group.py @@ -74,7 +74,6 @@ class UbiConnectWorker(Worker): def __init__(self, core: LegendaryCore, ubi_account_id, partner_link_id): super(UbiConnectWorker, self).__init__() self.signals = UbiConnectWorker.Signals() - self.setAutoDelete(True) self.core = core self.ubi_account_id = ubi_account_id self.partner_link_id = partner_link_id diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py index ec132120..5559b3df 100644 --- a/rare/components/tabs/settings/legendary.py +++ b/rare/components/tabs/settings/legendary.py @@ -1,7 +1,7 @@ -import platform +import platform as pf import re from logging import getLogger -from typing import Tuple +from typing import Tuple, List from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSettings from PyQt5.QtWidgets import QSizePolicy, QWidget, QFileDialog, QMessageBox @@ -19,14 +19,21 @@ class RefreshGameMetaWorker(Worker): class Signals(QObject): finished = pyqtSignal() - def __init__(self): + def __init__(self, platforms: List[str], include_unreal: bool): super(RefreshGameMetaWorker, self).__init__() self.signals = RefreshGameMetaWorker.Signals() - self.setAutoDelete(True) self.core = LegendaryCoreSingleton() + if platforms: + self.platforms = platforms + else: + self.platforms = ["Windows"] + self.skip_ue = not include_unreal def run_real(self) -> None: - self.core.get_game_and_dlc_list(True, force_refresh=True) + for platform in self.platforms: + self.core.get_game_and_dlc_list( + True, platform=platform, force_refresh=True, skip_ue=self.skip_ue + ) self.signals.finished.emit() @@ -34,7 +41,7 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): def __init__(self, parent=None): super(LegendarySettings, self).__init__(parent=parent) self.setupUi(self) - self.settings = QSettings() + self.settings = QSettings(self) self.core = LegendaryCoreSingleton() @@ -82,20 +89,36 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): ) self.locale_layout.addWidget(self.locale_edit) - self.win32_cb.setChecked(self.settings.value("win32_meta", False, bool)) - self.win32_cb.stateChanged.connect(lambda: self.settings.setValue("win32_meta", self.win32_cb.isChecked())) + self.fetch_win32_check.setChecked(self.settings.value("win32_meta", False, bool)) + self.fetch_win32_check.stateChanged.connect( + lambda: self.settings.setValue("win32_meta", self.fetch_win32_check.isChecked()) + ) - self.mac_cb.setChecked(self.settings.value("mac_meta", platform.system() == "Darwin", bool)) - self.mac_cb.stateChanged.connect(lambda: self.settings.setValue("mac_meta", self.mac_cb.isChecked())) + self.fetch_macos_check.setChecked(self.settings.value("macos_meta", pf.system() == "Darwin", bool)) + self.fetch_macos_check.stateChanged.connect( + lambda: self.settings.setValue("macos_meta", self.fetch_macos_check.isChecked()) + ) + self.fetch_macos_check.setDisabled(pf.system() == "Darwin") - self.refresh_game_meta_btn.clicked.connect(self.refresh_game_meta) + self.fetch_unreal_check.setChecked(self.settings.value("unreal_meta", False, bool)) + self.fetch_unreal_check.stateChanged.connect( + lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked()) + ) - def refresh_game_meta(self): - self.refresh_game_meta_btn.setDisabled(True) - self.refresh_game_meta_btn.setText(self.tr("Loading")) - worker = RefreshGameMetaWorker() - worker.signals.finished.connect(lambda: self.refresh_game_meta_btn.setDisabled(False)) - worker.signals.finished.connect(lambda: self.refresh_game_meta_btn.setText(self.tr("Refresh game meta"))) + 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) + self.refresh_metadata_button.setVisible(False) + + def refresh_metadata(self): + self.refresh_metadata_button.setDisabled(True) + platforms = [] + if self.fetch_win32_check.isChecked(): + platforms.append("Win32") + if self.fetch_macos_check.isChecked(): + platforms.append("Mac") + worker = RefreshGameMetaWorker(platforms, self.fetch_unreal_check.isChecked()) + worker.signals.finished.connect(lambda: self.refresh_metadata_button.setDisabled(False)) QThreadPool.globalInstance().start(worker) @staticmethod diff --git a/rare/lgndr/core.py b/rare/lgndr/core.py index 478d3128..ba6eaeb0 100644 --- a/rare/lgndr/core.py +++ b/rare/lgndr/core.py @@ -3,6 +3,7 @@ import json import logging import os from multiprocessing import Queue +from sys import platform as sys_platform from uuid import uuid4 # On Windows the monkeypatching of `run_real` below doesn't work like on Linux @@ -17,8 +18,8 @@ from legendary.models.game import Game, InstalledGame from legendary.models.manifest import ManifestMeta from rare.lgndr.downloader.mp.manager import DLManager -from rare.lgndr.lfs.lgndry import LGDLFS from rare.lgndr.glue.exception import LgndrException, LgndrLogHandler +from rare.lgndr.lfs.lgndry import LGDLFS legendary.core.DLManager = DLManager legendary.core.LGDLFS = LGDLFS @@ -50,6 +51,11 @@ class LegendaryCore(LegendaryCoreReal): return ret return unlock + @property + def default_platform(self) -> str: + os_default = "Mac" if sys_platform == "darwin" else "Windows" + return self.lgd.config.get("Legendary", "default_platform", fallback=os_default) + # skip_sync defaults to false but since Rare is persistent, skip by default # def get_installed_game(self, app_name, skip_sync=True) -> InstalledGame: # return super(LegendaryCore, self).get_installed_game(app_name, skip_sync) diff --git a/rare/models/game.py b/rare/models/game.py index 3d54eeef..0bca4d91 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -166,7 +166,8 @@ class RareGame(RareGameSlim): def update_game(self): self.game = self.core.get_game( - self.app_name, update_meta=False, platform=self.igame.platform if self.igame else "Windows" + self.app_name, update_meta=False, + platform=self.igame.platform if self.igame else self.core.default_platform ) def update_igame(self): @@ -228,7 +229,7 @@ class RareGame(RareGameSlim): if self.igame is not None: return self.game.app_version(self.igame.platform) else: - return self.game.app_version() + return self.game.app_version(self.core.default_platform) @property def has_update(self) -> bool: diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index abc68247..101ace50 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -1,5 +1,6 @@ import configparser import os +import platform import time from argparse import Namespace from itertools import chain @@ -25,6 +26,7 @@ from .workers import ( ) from .workers.uninstall import uninstall_game from .workers.worker import QueueWorkerInfo, QueueWorkerState +from rare.utils import config_helper logger = getLogger("RareCore") @@ -54,9 +56,10 @@ class RareCore(QObject): self.args(args) self.signals(init=True) self.core(init=True) + config_helper.init_config_handler(self.__core) self.image_manager(init=True) - self.settings = QSettings() + self.settings = QSettings(self) self.queue_workers: List[QueueWorker] = [] self.queue_threadpool = QThreadPool() @@ -140,6 +143,9 @@ class RareCore(QObject): for section in ["Legendary", "default", "default.env"]: if section not in self.__core.lgd.config.sections(): self.__core.lgd.config.add_section(section) + # Set some platform defaults + self.__core.lgd.config.set("Legendary", "default_platform", self.__core.default_platform) + self.__core.lgd.config.set("Legendary", "install_platform_fallback", False) # workaround if egl sync enabled, but no programdata_path # programdata_path might be unset if logging in through the browser if self.__core.egl_sync_enabled: @@ -357,7 +363,7 @@ class RareCore(QObject): yield game.game @property - def dlcs(self) -> Dict[str, Game]: + def dlcs(self) -> Dict[str, set[RareGame]]: """! RareGames that ARE DLCs themselves """ diff --git a/rare/shared/workers/fetch.py b/rare/shared/workers/fetch.py index 701e1890..3df8189a 100644 --- a/rare/shared/workers/fetch.py +++ b/rare/shared/workers/fetch.py @@ -1,9 +1,10 @@ +import platform import time from argparse import Namespace from enum import IntEnum from logging import getLogger -from PyQt5.QtCore import QObject, pyqtSignal +from PyQt5.QtCore import QObject, pyqtSignal, QSettings from requests.exceptions import ConnectionError, HTTPError from rare.lgndr.core import LegendaryCore @@ -27,19 +28,54 @@ class FetchWorker(Worker): self.signals = FetchWorker.Signals() self.core = core self.args = args + self.settings = QSettings() def run_real(self): # Fetch regular EGL games with assets - self.signals.progress.emit(0, self.signals.tr("Updating game metadata")) 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]) + + if want_win32 or self.args.debug: + logger.info( + "Requesting Win32 metadata due to %s, %s Unreal engine", + "settings" if want_win32 else "debug", + "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 + ) + + if need_macos or want_macos or self.args.debug: + logger.info( + "Requesting MacOS metadata due to %s, %s Unreal engine", + platform if need_macos else "settings" if want_macos else "debug", + "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 + ) + + if need_windows: + self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows")) + logger.info( + "Requesting Windows metadata, %s Unreal engine", + "with" if want_unreal else "without" + ) games, dlc_dict = self.core.get_game_and_dlc_list( - update_assets=not self.args.offline, platform="Windows", skip_ue=False + 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") # Fetch non-asset games - self.signals.progress.emit(10, self.signals.tr("Updating non-asset metadata")) + 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) diff --git a/rare/ui/components/tabs/settings/legendary.py b/rare/ui/components/tabs/settings/legendary.py index 634f5efa..f2e71e87 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(595, 334) + LegendarySettings.resize(681, 456) LegendarySettings.setWindowTitle("LegendarySettings") self.legendary_layout = QtWidgets.QHBoxLayout(LegendarySettings) self.legendary_layout.setObjectName("legendary_layout") @@ -125,21 +125,30 @@ class Ui_LegendarySettings(object): self.clean_button = QtWidgets.QPushButton(self.cleanup_group) self.clean_button.setObjectName("clean_button") self.cleanup_layout.addWidget(self.clean_button) - self.right_layout.addWidget(self.cleanup_group, 0, QtCore.Qt.AlignTop) - self.meta_group = QtWidgets.QGroupBox(LegendarySettings) - self.meta_group.setObjectName("meta_group") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.meta_group) + 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.win32_cb = QtWidgets.QCheckBox(self.meta_group) - self.win32_cb.setObjectName("win32_cb") - self.verticalLayout_2.addWidget(self.win32_cb) - self.mac_cb = QtWidgets.QCheckBox(self.meta_group) - self.mac_cb.setObjectName("mac_cb") - self.verticalLayout_2.addWidget(self.mac_cb) - self.refresh_game_meta_btn = QtWidgets.QPushButton(self.meta_group) - self.refresh_game_meta_btn.setObjectName("refresh_game_meta_btn") - self.verticalLayout_2.addWidget(self.refresh_game_meta_btn) - self.right_layout.addWidget(self.meta_group) + 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.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.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.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.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.right_layout.addWidget(self.metadata_group) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.right_layout.addItem(spacerItem1) self.legendary_layout.addLayout(self.right_layout) @@ -162,10 +171,12 @@ class Ui_LegendarySettings(object): self.cleanup_group.setTitle(_translate("LegendarySettings", "Cleanup")) self.clean_keep_manifests_button.setText(_translate("LegendarySettings", "Clean, but keep manifests")) self.clean_button.setText(_translate("LegendarySettings", "Remove everything")) - self.meta_group.setTitle(_translate("LegendarySettings", "Game metadata")) - self.win32_cb.setText(_translate("LegendarySettings", "Load 32bit data")) - self.mac_cb.setText(_translate("LegendarySettings", "Load MacOS data")) - self.refresh_game_meta_btn.setText(_translate("LegendarySettings", "Refresh game meta")) + self.metadata_group.setTitle(_translate("LegendarySettings", "Platforms")) + 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.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply")) + self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/settings/legendary.ui b/rare/ui/components/tabs/settings/legendary.ui index a69ef823..b87929da 100644 --- a/rare/ui/components/tabs/settings/legendary.ui +++ b/rare/ui/components/tabs/settings/legendary.ui @@ -6,8 +6,8 @@ 0 0 - 595 - 334 + 681 + 456 @@ -200,7 +200,7 @@ - + Cleanup @@ -227,29 +227,48 @@ - + - Game metadata + Platforms - + - Load 32bit data + Include Win32 games - + - Load MacOS data + Include MacOS games - + - Refresh game meta + Include Unreal engine + + + + + + + + true + + + + Restart Rare to apply + + + + + + + Refresh metadata From 76083b9f073109685661318fb8525b149d378391 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:58:03 +0200 Subject: [PATCH 08/34] Ui: Update some strings in RareSettings form --- rare/ui/components/tabs/settings/rare.py | 24 ++++++++++++------------ rare/ui/components/tabs/settings/rare.ui | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/rare/ui/components/tabs/settings/rare.py b/rare/ui/components/tabs/settings/rare.py index fc0bc69f..a78538da 100644 --- a/rare/ui/components/tabs/settings/rare.py +++ b/rare/ui/components/tabs/settings/rare.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/rare.ui' # -# Created by: PyQt5 UI code generator 5.15.6 +# 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_RareSettings(object): def setupUi(self, RareSettings): RareSettings.setObjectName("RareSettings") - RareSettings.resize(517, 434) + RareSettings.resize(623, 428) RareSettings.setWindowTitle("RareSettings") self.rare_layout = QtWidgets.QHBoxLayout(RareSettings) self.rare_layout.setObjectName("rare_layout") @@ -155,18 +155,18 @@ class Ui_RareSettings(object): self.interface_info.setText(_translate("RareSettings", "Restart Rare to apply.")) self.settings_group.setTitle(_translate("RareSettings", "Behavior")) self.save_size.setText(_translate("RareSettings", "Restore window size on application startup")) - self.notification.setText(_translate("RareSettings", "Show notification on download completion")) - self.log_games.setText(_translate("RareSettings", "Show console for game debug")) - self.sys_tray.setText(_translate("RareSettings", "Exit to System tray")) - self.auto_update.setText(_translate("RareSettings", "Update games on application startup")) - self.confirm_start.setText(_translate("RareSettings", "Confirm game launch")) - self.auto_sync_cloud.setText(_translate("RareSettings", "Automatically sync with cloud")) + self.notification.setText(_translate("RareSettings", "Show notifications when downloads complete")) + self.log_games.setText(_translate("RareSettings", "Show console windows when launching games")) + self.sys_tray.setText(_translate("RareSettings", "Exit to system tray")) + self.auto_update.setText(_translate("RareSettings", "Queue game updates on application startup")) + self.confirm_start.setText(_translate("RareSettings", "Confirm before launching games")) + self.auto_sync_cloud.setText(_translate("RareSettings", "Automatically upload/download cloud saves")) self.log_dir_group.setTitle(_translate("RareSettings", "Logs")) - self.log_dir_open_button.setText(_translate("RareSettings", "Open Log directory")) - self.log_dir_clean_button.setText(_translate("RareSettings", "Clean Log directory")) + self.log_dir_open_button.setText(_translate("RareSettings", "Open log folder")) + self.log_dir_clean_button.setText(_translate("RareSettings", "Clean log folder")) self.groupBox.setTitle(_translate("RareSettings", "Shortcuts")) - self.desktop_link_btn.setText(_translate("RareSettings", "Create Desktop link")) - self.startmenu_link_btn.setText(_translate("RareSettings", "Create start menu link")) + self.desktop_link_btn.setText(_translate("RareSettings", "Create on desktop")) + self.startmenu_link_btn.setText(_translate("RareSettings", "Create in menu")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/settings/rare.ui b/rare/ui/components/tabs/settings/rare.ui index 5f89464d..820a0027 100644 --- a/rare/ui/components/tabs/settings/rare.ui +++ b/rare/ui/components/tabs/settings/rare.ui @@ -6,8 +6,8 @@ 0 0 - 517 - 434 + 623 + 428 @@ -126,42 +126,42 @@ - Show notification on download completion + Show notifications when downloads complete - Show console for game debug + Show console windows when launching games - Exit to System tray + Exit to system tray - Update games on application startup + Queue game updates on application startup - Confirm game launch + Confirm before launching games - Automatically sync with cloud + Automatically upload/download cloud saves @@ -207,14 +207,14 @@ - Open Log directory + Open log folder - Clean Log directory + Clean log folder @@ -246,14 +246,14 @@ - Create Desktop link + Create on desktop - Create start menu link + Create in menu From 51337116d523aa72f1c0f78791c45461baa02a6c Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:25:09 +0200 Subject: [PATCH 09/34] RareGameBase: use default platform for version --- rare/models/base_game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rare/models/base_game.py b/rare/models/base_game.py index b3eff1bf..77885347 100644 --- a/rare/models/base_game.py +++ b/rare/models/base_game.py @@ -169,7 +169,7 @@ class RareGameBase(QObject): @return str The current version of the game """ - return self.igame.version if self.igame is not None else self.game.app_version() + return self.igame.version if self.igame is not None else self.game.app_version(self.core.default_platform) @property def install_path(self) -> Optional[str]: From cada2ae985fcfb2fd722554ff1417acc35efa94d Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:56:33 +0200 Subject: [PATCH 10/34] ImportWorker: Use default platform when importing, fallback to Windows if there is no assets for the platform --- .../tabs/games/integrations/import_group.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/rare/components/tabs/games/integrations/import_group.py b/rare/components/tabs/games/integrations/import_group.py index 24e05dff..89dfa929 100644 --- a/rare/components/tabs/games/integrations/import_group.py +++ b/rare/components/tabs/games/integrations/import_group.py @@ -111,8 +111,8 @@ class ImportWorker(QRunnable): result.path = str(path) if app_name or (app_name := find_app_name(str(path), self.core)): result.app_name = app_name - result.app_title = app_title = self.core.get_game(app_name).app_title - success, message = self.__import_game(path, app_name, app_title) + result.app_title = self.core.get_game(app_name).app_title + success, message = self.__import_game(path, app_name) if not success: result.result = ImportResult.FAILED result.message = message @@ -120,12 +120,18 @@ class ImportWorker(QRunnable): result.result = ImportResult.SUCCESS return result - def __import_game(self, path: Path, app_name: str, app_title: str): + def __import_game(self, path: Path, app_name: str): cli = LegendaryCLI(self.core) status = LgndrIndirectStatus() + # FIXME: Add override option in import form + platform = self.core.default_platform + if platform not in self.core.get_game(app_name, update_meta=False).asset_infos: + platform = "Windows" + args = LgndrImportGameArgs( app_path=str(path), app_name=app_name, + platform=platform, disable_check=self.import_force, skip_dlcs=not self.import_dlcs, with_dlcs=self.import_dlcs, From 1b85440e2b0326fc19327c753bb33ade5b338d78 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:20:32 +0200 Subject: [PATCH 11/34] IndicatorEdit: Set placeholder as tooltip too --- rare/widgets/indicator_edit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rare/widgets/indicator_edit.py b/rare/widgets/indicator_edit.py index c9a76bb6..9c31a737 100644 --- a/rare/widgets/indicator_edit.py +++ b/rare/widgets/indicator_edit.py @@ -4,7 +4,6 @@ from logging import getLogger from typing import Callable, Tuple, Optional, Dict, List from PyQt5.QtCore import ( - Qt, QSize, pyqtSignal, QFileInfo, @@ -135,7 +134,8 @@ class IndicatorLineEdit(QWidget): # Add line_edit self.line_edit = QLineEdit(self) self.line_edit.setObjectName(f"{type(self).__name__}Edit") - self.line_edit.setPlaceholderText(placeholder if placeholder else self.tr("Default")) + self.line_edit.setPlaceholderText(placeholder if placeholder else self.tr("Use global/default setting")) + self.line_edit.setToolTip(placeholder if placeholder else "") self.line_edit.setSizePolicy(horiz_policy, QSizePolicy.Fixed) # Add completer self.setCompleter(completer) From fd809c708f4cf126046b35444058415410aba19c Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:22:55 +0200 Subject: [PATCH 12/34] UI: Update some strings --- rare/ui/components/tabs/settings/legendary.py | 10 +++++----- rare/ui/components/tabs/settings/legendary.ui | 10 +++++----- rare/ui/components/tabs/settings/rare.py | 6 +++--- rare/ui/components/tabs/settings/rare.ui | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/rare/ui/components/tabs/settings/legendary.py b/rare/ui/components/tabs/settings/legendary.py index f2e71e87..829a60ab 100644 --- a/rare/ui/components/tabs/settings/legendary.py +++ b/rare/ui/components/tabs/settings/legendary.py @@ -157,11 +157,11 @@ class Ui_LegendarySettings(object): def retranslateUi(self, LegendarySettings): _translate = QtCore.QCoreApplication.translate - self.install_dir_group.setTitle(_translate("LegendarySettings", "Default Installation Directory")) - self.download_group.setTitle(_translate("LegendarySettings", "Download Settings")) - self.max_workers_label.setText(_translate("LegendarySettings", "Max Workers")) + self.install_dir_group.setTitle(_translate("LegendarySettings", "Default installation folder")) + self.download_group.setTitle(_translate("LegendarySettings", "Download settings")) + self.max_workers_label.setText(_translate("LegendarySettings", "Max sorkers")) self.max_workers_info_label.setText(_translate("LegendarySettings", "Less is slower (0: Default)")) - self.max_memory_label.setText(_translate("LegendarySettings", "Max Shared Memory")) + self.max_memory_label.setText(_translate("LegendarySettings", "Max shared memory")) self.max_memory_spin.setSuffix(_translate("LegendarySettings", "MiB")) self.max_memory_info_label.setText(_translate("LegendarySettings", "Less is slower (0: Default)")) self.preferred_cdn_label.setText(_translate("LegendarySettings", "Preferred CDN")) @@ -173,7 +173,7 @@ class Ui_LegendarySettings(object): self.clean_button.setText(_translate("LegendarySettings", "Remove everything")) self.metadata_group.setTitle(_translate("LegendarySettings", "Platforms")) self.fetch_win32_check.setText(_translate("LegendarySettings", "Include Win32 games")) - self.fetch_macos_check.setText(_translate("LegendarySettings", "Include MacOS games")) + self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games")) self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine")) 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 b87929da..65e69f1c 100644 --- a/rare/ui/components/tabs/settings/legendary.ui +++ b/rare/ui/components/tabs/settings/legendary.ui @@ -19,7 +19,7 @@ - Default Installation Directory + Default installation folder Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -30,7 +30,7 @@ - Download Settings + Download settings Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -48,7 +48,7 @@ - Max Workers + Max sorkers @@ -90,7 +90,7 @@ - Max Shared Memory + Max shared memory @@ -242,7 +242,7 @@ - Include MacOS games + Include macOS games diff --git a/rare/ui/components/tabs/settings/rare.py b/rare/ui/components/tabs/settings/rare.py index a78538da..fc1abaff 100644 --- a/rare/ui/components/tabs/settings/rare.py +++ b/rare/ui/components/tabs/settings/rare.py @@ -148,11 +148,11 @@ class Ui_RareSettings(object): _translate = QtCore.QCoreApplication.translate self.interface_group.setTitle(_translate("RareSettings", "Interface")) self.lang_label.setText(_translate("RareSettings", "Language")) - self.color_label.setText(_translate("RareSettings", "Color Scheme")) + self.color_label.setText(_translate("RareSettings", "Color scheme")) self.color_select.setItemText(0, _translate("RareSettings", "None")) - self.style_label.setText(_translate("RareSettings", "Style Sheet")) + self.style_label.setText(_translate("RareSettings", "Style sheet")) self.style_select.setItemText(0, _translate("RareSettings", "None")) - self.interface_info.setText(_translate("RareSettings", "Restart Rare to apply.")) + self.interface_info.setText(_translate("RareSettings", "Restart Rare to apply changes.")) self.settings_group.setTitle(_translate("RareSettings", "Behavior")) self.save_size.setText(_translate("RareSettings", "Restore window size on application startup")) self.notification.setText(_translate("RareSettings", "Show notifications when downloads complete")) diff --git a/rare/ui/components/tabs/settings/rare.ui b/rare/ui/components/tabs/settings/rare.ui index 820a0027..2178d31d 100644 --- a/rare/ui/components/tabs/settings/rare.ui +++ b/rare/ui/components/tabs/settings/rare.ui @@ -51,7 +51,7 @@ - Color Scheme + Color scheme @@ -73,7 +73,7 @@ - Style Sheet + Style sheet @@ -100,7 +100,7 @@ - Restart Rare to apply. + Restart Rare to apply changes. true From 392b4b7896913d09b19bcc49dfafe4532746d460 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:23:32 +0200 Subject: [PATCH 13/34] RareSettings: Always show information label in interface section --- rare/components/tabs/settings/rare.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rare/components/tabs/settings/rare.py b/rare/components/tabs/settings/rare.py index 25b8c447..1c42040a 100644 --- a/rare/components/tabs/settings/rare.py +++ b/rare/components/tabs/settings/rare.py @@ -82,8 +82,6 @@ class RareSettings(QWidget, Ui_RareSettings): self.style_select.setCurrentIndex(0) self.style_select.currentIndexChanged.connect(self.on_style_select_changed) - self.interface_info.setVisible(False) - self.rpc = RPCSettings(self) self.right_layout.insertWidget(1, self.rpc, alignment=Qt.AlignTop) @@ -203,7 +201,6 @@ class RareSettings(QWidget, Ui_RareSettings): self.settings.setValue("color_scheme", "") self.style_select.setDisabled(False) set_color_pallete("") - self.interface_info.setVisible(True) def on_style_select_changed(self, style): if style: @@ -215,7 +212,6 @@ class RareSettings(QWidget, Ui_RareSettings): self.settings.setValue("style_sheet", "") self.color_select.setDisabled(False) set_style_sheet("") - self.interface_info.setVisible(True) def open_dir(self): if platform.system() == "Windows": @@ -230,7 +226,6 @@ class RareSettings(QWidget, Ui_RareSettings): def update_lang(self, i: int): self.settings.setValue("language", languages[i][0]) - self.interface_info.setVisible(True) def init_checkboxes(self, checkboxes): for cb in checkboxes: From 6d5b60a56ef794004f6608decac0b84013adb592 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:28:49 +0200 Subject: [PATCH 14/34] LegendarySettings: Add path edit for macOS specific `mac_install_dir` option Used only when installing macOS games on macOS, in any other OS the setting is set to the same value as `install_dir` --- rare/components/tabs/settings/legendary.py | 33 +++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py index 5559b3df..c4254608 100644 --- a/rare/components/tabs/settings/legendary.py +++ b/rare/components/tabs/settings/legendary.py @@ -45,11 +45,22 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): self.core = LegendaryCoreSingleton() - # Default installation directory + # Platform specific installation directory for macOS games + if pf.system() == "Darwin": + self.mac_install_dir = PathEdit( + self.core.get_default_install_dir("Mac"), + placeholder=self.tr("Default installation folder for macOS games"), + file_mode=QFileDialog.DirectoryOnly, + save_func=self.__mac_path_save, + ) + self.install_dir_layout.addWidget(self.mac_install_dir) + + # Platform-independent installation directory self.install_dir = PathEdit( self.core.get_default_install_dir(), + placeholder=self.tr("Default installation folder for Windows games"), file_mode=QFileDialog.DirectoryOnly, - save_func=self.path_save, + save_func=self.__win_path_save, ) self.install_dir_layout.addWidget(self.install_dir) @@ -143,12 +154,20 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): self.core.lgd.config.remove_option("Legendary", "locale") self.core.lgd.save_config() - def path_save(self, text: str): - self.core.lgd.config["Legendary"]["install_dir"] = text - if not text and "install_dir" in self.core.lgd.config["Legendary"].keys(): - self.core.lgd.config["Legendary"].pop("install_dir") + def __mac_path_save(self, text: str) -> None: + self.__path_save(text, "mac_install_dir") + + def __win_path_save(self, text: str) -> None: + self.__path_save(text, "install_dir") + if pf.system() != "Darwin": + self.__mac_path_save(text) + + def __path_save(self, text: str, option: str = "Windows"): + self.core.lgd.config["Legendary"][option] = text + if not text and option in self.core.lgd.config["Legendary"].keys(): + self.core.lgd.config["Legendary"].pop(option) else: - logger.debug(f"Set config install_dir to {text}") + logger.debug(f"Set %s option in config to %s", option, text) self.core.lgd.save_config() def max_worker_save(self, workers: str): From 097b76fc6b0cec1bfc7837c87b1fd307ba0945de Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 15 Dec 2023 23:30:13 +0200 Subject: [PATCH 15/34] FetchWorker: Update strings `MacOS` -> `macOS` --- rare/shared/workers/fetch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rare/shared/workers/fetch.py b/rare/shared/workers/fetch.py index 3df8189a..b18ea83f 100644 --- a/rare/shared/workers/fetch.py +++ b/rare/shared/workers/fetch.py @@ -53,11 +53,11 @@ class FetchWorker(Worker): if need_macos or want_macos or self.args.debug: logger.info( - "Requesting MacOS metadata due to %s, %s Unreal engine", - platform if need_macos else "settings" if want_macos else "debug", + "Requesting macOS metadata due to %s, %s Unreal engine", + "platform" if need_macos else "settings" if want_macos else "debug", "with" if want_unreal else "without" ) - self.signals.progress.emit(15, self.signals.tr("Updating game metadata for MacOS")) + 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 ) From c3bdffcd50d7817c13cd650ae88b69478d5362f9 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 00:39:00 +0200 Subject: [PATCH 16/34] RareCore: Check if LEGENDARY_CONFIG_PATH is set before creating a new config --- rare/shared/rare_core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index 101ace50..0486d271 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -133,10 +133,12 @@ class RareCore(QObject): self.__core = LegendaryCore() except configparser.MissingSectionHeaderError as e: logger.warning(f"Config is corrupt: {e}") - if config_path := os.environ.get("XDG_CONFIG_HOME"): - path = os.path.join(config_path, "legendary") + if config_path := os.environ.get('LEGENDARY_CONFIG_PATH'): + path = config_path + elif config_path := os.environ.get('XDG_CONFIG_HOME'): + path = os.path.join(config_path, 'legendary') else: - path = os.path.expanduser("~/.config/legendary") + path = os.path.expanduser('~/.config/legendary') with open(os.path.join(path, "config.ini"), "w") as config_file: config_file.write("[Legendary]") self.__core = LegendaryCore() From db588ed4e2af89d2ff1679f3e62dcc72c34cf2d6 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 00:40:15 +0200 Subject: [PATCH 17/34] Use legendary's installation directory inference --- rare/components/tabs/games/integrations/eos_group.py | 4 +--- rare/components/tabs/games/integrations/import_group.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/rare/components/tabs/games/integrations/eos_group.py b/rare/components/tabs/games/integrations/eos_group.py index 784d4ae4..174f34aa 100644 --- a/rare/components/tabs/games/integrations/eos_group.py +++ b/rare/components/tabs/games/integrations/eos_group.py @@ -213,9 +213,7 @@ class EOSGroup(QGroupBox, Ui_EosWidget): self.enabled_cb.setChecked(enabled) def install_overlay(self, update=False): - base_path = os.path.join( - self.core.lgd.config.get("Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")),".overlay" - ) + base_path = os.path.join(self.core.get_default_install_dir(), ".overlay") if update: if not self.overlay: self.overlay_stack.setCurrentIndex(1) diff --git a/rare/components/tabs/games/integrations/import_group.py b/rare/components/tabs/games/integrations/import_group.py index 89dfa929..c9034ab4 100644 --- a/rare/components/tabs/games/integrations/import_group.py +++ b/rare/components/tabs/games/integrations/import_group.py @@ -198,7 +198,7 @@ class ImportGroup(QGroupBox): self.__install_dirs: Set[str] = set() self.path_edit = PathEdit( - self.core.get_default_install_dir(), + self.core.get_default_install_dir(self.core.default_platform), QFileDialog.DirectoryOnly, edit_func=self.path_edit_callback, parent=self, From 6249e568495d5eeec337f6c053d186f73ffe1096 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 00:49:51 +0200 Subject: [PATCH 18/34] InstallDialog: Oooooooof.gif * Infer the displayed to reflect where the affected directory is If base_path is set outside of InstallDialog, display that. If the game is already installed, show the installation directory If neither of the above is true, use legendary's inference based on the default platform if the game supports it. Fallback to Windows. * Disable irrelevant and potentially harmful options when the game is already installed, such as the installation path, the platform selection and creating a shortcut. * Infer the correct platform based on the existing installation. If it is not installed, use the default platform if the game supports it and fallback to windows. * Move the horrible lambda used to populate the error box when the platform was unsupported into a separate method. --- rare/components/dialogs/install_dialog.py | 91 ++++++++++++++--------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index 52e0aab2..681cdbad 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -73,13 +73,17 @@ class InstallDialog(QDialog): self.ui.install_dialog_label.setText(f'

{header} "{self.rgame.app_title}"

') self.setWindowTitle(f'{header} "{self.rgame.app_title}" - {QCoreApplication.instance().applicationName()}') - if not self.options.base_path: - self.options.base_path = self.core.lgd.config.get( - "Legendary", "install_dir", fallback=os.path.expanduser("~/legendary") + if options.base_path: + base_path = options.base_path + elif rgame.is_installed: + base_path = rgame.install_path + else: + base_path = self.core.get_default_install_dir( + self.core.default_platform if self.core.default_platform in rgame.platforms else "Windows" ) self.install_dir_edit = PathEdit( - path=self.options.base_path, + path=base_path, file_mode=QFileDialog.DirectoryOnly, edit_func=self.option_changed, save_func=self.save_install_edit, @@ -90,36 +94,30 @@ class InstallDialog(QDialog): QFormLayout.FieldRole, self.install_dir_edit ) - if self.options.update: - self.ui.install_dir_label.setEnabled(False) - self.install_dir_edit.setEnabled(False) - self.ui.shortcut_label.setEnabled(False) - self.ui.shortcut_check.setEnabled(False) - else: - self.ui.shortcut_check.setChecked(QSettings().value("create_shortcut", True, bool)) + self.install_dir_edit.setDisabled(rgame.is_installed) + self.ui.install_dir_label.setDisabled(rgame.is_installed) + self.ui.shortcut_label.setDisabled(rgame.is_installed) + self.ui.shortcut_check.setDisabled(rgame.is_installed) + self.ui.shortcut_check.setChecked(not rgame.is_installed and QSettings().value("create_shortcut", True, bool)) self.error_box() - platforms = self.rgame.platforms - self.ui.platform_combo.addItems(reversed(platforms)) - self.ui.platform_combo.currentIndexChanged.connect(lambda: self.option_changed(None)) - self.ui.platform_combo.currentIndexChanged.connect(lambda: self.error_box()) - self.ui.platform_combo.currentIndexChanged.connect( - lambda i: self.error_box( - self.tr("Warning"), - self.tr("You will not be able to run the game if you select {} as platform").format( - self.ui.platform_combo.itemText(i) - ), - ) - if (self.ui.platform_combo.currentText() == "Mac" and pf.system() != "Darwin") - else None + self.ui.platform_combo.addItems(reversed(rgame.platforms)) + combo_text = ( + rgame.igame.platform + if rgame.is_installed + else self.core.default_platform + if self.core.default_platform in rgame.platforms + else "Windows" ) - self.ui.platform_combo.setCurrentIndex( - self.ui.platform_combo.findText( - "Mac" if (pf.system() == "Darwin" and "Mac" in platforms) else "Windows" - ) - ) - self.ui.platform_combo.currentTextChanged.connect(self.setup_sdl_list) + 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.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_label.setDisabled(rgame.is_installed) + self.ui.platform_combo.setDisabled(rgame.is_installed) self.advanced.ui.max_workers_spin.setValue(self.core.lgd.config.getint("Legendary", "max_workers", fallback=0)) self.advanced.ui.max_workers_spin.valueChanged.connect(self.option_changed) @@ -139,7 +137,10 @@ class InstallDialog(QDialog): self.selectable_checks: List[TagCheckBox] = [] self.config_tags: Optional[List[str]] = None - self.setup_sdl_list(self.ui.platform_combo.currentText()) + + self.reset_install_dir(self.ui.platform_combo.currentIndex()) + self.reset_sdl_list(self.ui.platform_combo.currentIndex()) + self.check_incompatible_platform(self.ui.platform_combo.currentIndex()) self.ui.install_button.setEnabled(False) @@ -155,9 +156,10 @@ class InstallDialog(QDialog): self.selectable.setEnabled(False) if pf.system() == "Darwin": + self.ui.shortcut_label.setDisabled(True) self.ui.shortcut_check.setDisabled(True) self.ui.shortcut_check.setChecked(False) - self.ui.shortcut_check.setToolTip(self.tr("Creating a shortcut is not supported on MacOS")) + self.ui.shortcut_check.setToolTip(self.tr("Creating a shortcut is not supported on macOS")) self.advanced.ui.install_prereqs_label.setEnabled(False) self.advanced.ui.install_prereqs_check.setEnabled(False) @@ -190,8 +192,16 @@ class InstallDialog(QDialog): self.__on_verify() self.show() - @pyqtSlot(str) - def setup_sdl_list(self, platform="Windows"): + @pyqtSlot(int) + def reset_install_dir(self, index: int): + if not self.rgame.is_installed: + platform = self.ui.platform_combo.itemText(index) + 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() @@ -223,8 +233,19 @@ class InstallDialog(QDialog): 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) + ) + else: + self.error_box() + def get_options(self): - self.options.base_path = self.install_dir_edit.text() if not self.options.update else None + self.options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text() 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() From 0bd34af720e5d5870d3d75cb48efd562c6586852 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 01:28:37 +0200 Subject: [PATCH 19/34] ImportGroup: calculate correct install dir when importing game --- rare/components/tabs/games/integrations/import_group.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rare/components/tabs/games/integrations/import_group.py b/rare/components/tabs/games/integrations/import_group.py index c9034ab4..e56aee0d 100644 --- a/rare/components/tabs/games/integrations/import_group.py +++ b/rare/components/tabs/games/integrations/import_group.py @@ -253,8 +253,10 @@ class ImportGroup(QGroupBox): def set_game(self, app_name: str): if app_name: - folder = self.rcore.get_game(app_name).folder_name - self.path_edit.setText(os.path.join(self.core.get_default_install_dir(), folder)) + game = self.rcore.get_game(app_name) + folder = game.folder_name + platform = self.core.default_platform if self.core.default_platform in game.platforms else "Windows" + self.path_edit.setText(os.path.join(self.core.get_default_install_dir(platform), folder)) self.app_name_edit.setText(app_name) def path_edit_callback(self, path) -> Tuple[bool, str, int]: From 79df1463485fd1c7ac0dc58de4b579d1a742af1c Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 13:50:48 +0200 Subject: [PATCH 20/34] Rare: expand `LEGENDARY_CONFIG_PATH` before using it. --- rare/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rare/main.py b/rare/main.py index 54c2d661..5bf89d58 100755 --- a/rare/main.py +++ b/rare/main.py @@ -18,6 +18,8 @@ def main() -> int: sys.stderr = open(os.devnull, 'w') os.environ["QT_QPA_PLATFORMTHEME"] = "" + if "LEGENDARY_CONFIG_PATH" in os.environ: + os.environ["LEGENDARY_CONFIG_PATH"] = os.path.expanduser(os.environ["LEGENDARY_CONFIG_PATH"]) # fix cx_freeze multiprocessing.freeze_support() From 8e573083adc87908bf97756fad6d54f4a7e66230 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 13:52:06 +0200 Subject: [PATCH 21/34] RareCore: Set `install_dir` and `mac_install_dir` on new configurations. --- rare/shared/rare_core.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index 0486d271..d1606af2 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -132,13 +132,14 @@ class RareCore(QObject): try: self.__core = LegendaryCore() except configparser.MissingSectionHeaderError as e: - logger.warning(f"Config is corrupt: {e}") + logger.warning("Config is corrupt: %s", e) if config_path := os.environ.get('LEGENDARY_CONFIG_PATH'): path = config_path elif config_path := os.environ.get('XDG_CONFIG_HOME'): path = os.path.join(config_path, 'legendary') else: path = os.path.expanduser('~/.config/legendary') + logger.info("Creating config in path: %s", config_path) with open(os.path.join(path, "config.ini"), "w") as config_file: config_file.write("[Legendary]") self.__core = LegendaryCore() @@ -146,6 +147,12 @@ class RareCore(QObject): if section not in self.__core.lgd.config.sections(): self.__core.lgd.config.add_section(section) # Set some platform defaults + self.__core.lgd.config.set( + "Legendary", "install_dir", self.__core.get_default_install_dir() + ) + self.__core.lgd.config.set( + "Legendary", "mac_install_dir", self.__core.get_default_install_dir(self.__core.default_platform) + ) self.__core.lgd.config.set("Legendary", "default_platform", self.__core.default_platform) self.__core.lgd.config.set("Legendary", "install_platform_fallback", False) # workaround if egl sync enabled, but no programdata_path From 1139765343c7280b4b3caadc4b3a43d9fe0329e6 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 13:52:37 +0200 Subject: [PATCH 22/34] ImageSize: Make Library images slightly larger to accommodate more text on macOS --- 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 b32fa747..68a87c9e 100644 --- a/rare/models/image.py +++ b/rare/models/image.py @@ -70,9 +70,9 @@ class ImageSize: DisplayWide = Preset(1, 1, Orientation.Wide, base=ImageWide) """! @brief Size and pixel ratio for wide 16/9 image display""" - LibraryWide = Preset(1.33, 1, Orientation.Wide, base=ImageWide) + LibraryWide = Preset(1.21, 1, Orientation.Wide, base=ImageWide) - Library = Preset(1.33, 1, base=Image) + Library = Preset(1.21, 1, base=Image) """! @brief Same as Display""" Small = Preset(3, 1, base=Image) From 447d704de305e85b0fa810ee71418dd1c6938a7e Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 13:57:32 +0200 Subject: [PATCH 23/34] Lgndr: Log what config location we are using --- rare/lgndr/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rare/lgndr/core.py b/rare/lgndr/core.py index ba6eaeb0..68ce36de 100644 --- a/rare/lgndr/core.py +++ b/rare/lgndr/core.py @@ -31,6 +31,7 @@ class LegendaryCore(LegendaryCoreReal): def __init__(self, *args, **kwargs): super(LegendaryCore, self).__init__(*args, **kwargs) self.log.info("Using Rare's LegendaryCore monkey") + self.log.info("Using config in %s", self.lgd.path.replace(os.getlogin(), "")) self.handler = LgndrLogHandler(logging.CRITICAL) self.log.addHandler(self.handler) From 5f5e471169bf639dee02e0e53b209002089cee8e Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 14:02:53 +0200 Subject: [PATCH 24/34] RareGameBase: Add `default_platform` property This property reports the default platform to use for a game based on legendary's configuration and if they platform is available in the game's assets. Using that property we can make better choices on what platform to operate on without user intervention. Currently we use it to infer the platform in when installing, importing, and calculating game versions. --- rare/components/dialogs/install_dialog.py | 12 ++---------- rare/components/tabs/games/game_info/game_info.py | 2 +- .../tabs/games/integrations/import_group.py | 8 ++++---- rare/models/base_game.py | 6 +++++- rare/models/game.py | 4 ++-- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index 681cdbad..a3042281 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -78,9 +78,7 @@ class InstallDialog(QDialog): elif rgame.is_installed: base_path = rgame.install_path else: - base_path = self.core.get_default_install_dir( - self.core.default_platform if self.core.default_platform in rgame.platforms else "Windows" - ) + base_path = self.core.get_default_install_dir(rgame.default_platform) self.install_dir_edit = PathEdit( path=base_path, @@ -103,13 +101,7 @@ class InstallDialog(QDialog): self.error_box() self.ui.platform_combo.addItems(reversed(rgame.platforms)) - combo_text = ( - rgame.igame.platform - if rgame.is_installed - else self.core.default_platform - if self.core.default_platform in rgame.platforms - else "Windows" - ) + 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.check_incompatible_platform) diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 44e5b691..f0507021 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -286,7 +286,7 @@ class GameInfo(QWidget, SideTabContents): self.ui.platform.setText( self.rgame.igame.platform if self.rgame.is_installed and not self.rgame.is_non_asset - else self.core.default_platform + else self.rgame.default_platform ) self.ui.lbl_grade.setDisabled( diff --git a/rare/components/tabs/games/integrations/import_group.py b/rare/components/tabs/games/integrations/import_group.py index e56aee0d..ae4eede6 100644 --- a/rare/components/tabs/games/integrations/import_group.py +++ b/rare/components/tabs/games/integrations/import_group.py @@ -253,10 +253,10 @@ class ImportGroup(QGroupBox): def set_game(self, app_name: str): if app_name: - game = self.rcore.get_game(app_name) - folder = game.folder_name - platform = self.core.default_platform if self.core.default_platform in game.platforms else "Windows" - self.path_edit.setText(os.path.join(self.core.get_default_install_dir(platform), folder)) + rgame = self.rcore.get_game(app_name) + self.path_edit.setText( + os.path.join(self.core.get_default_install_dir(rgame.default_platform), rgame.folder_name) + ) self.app_name_edit.setText(app_name) def path_edit_callback(self, path) -> Tuple[bool, str, int]: diff --git a/rare/models/base_game.py b/rare/models/base_game.py index 77885347..0a1ee7ab 100644 --- a/rare/models/base_game.py +++ b/rare/models/base_game.py @@ -121,6 +121,10 @@ class RareGameBase(QObject): """ return tuple(self.game.asset_infos.keys()) + @property + def default_platform(self) -> str: + return self.core.default_platform if self.core.default_platform in self.platforms else "Windows" + @property def is_mac(self) -> bool: """! @@ -169,7 +173,7 @@ class RareGameBase(QObject): @return str The current version of the game """ - return self.igame.version if self.igame is not None else self.game.app_version(self.core.default_platform) + return self.igame.version if self.igame is not None else self.game.app_version(self.default_platform) @property def install_path(self) -> Optional[str]: diff --git a/rare/models/game.py b/rare/models/game.py index 0bca4d91..e3b19044 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -167,7 +167,7 @@ class RareGame(RareGameSlim): def update_game(self): self.game = self.core.get_game( self.app_name, update_meta=False, - platform=self.igame.platform if self.igame else self.core.default_platform + platform=self.igame.platform if self.igame else self.default_platform ) def update_igame(self): @@ -229,7 +229,7 @@ class RareGame(RareGameSlim): if self.igame is not None: return self.game.app_version(self.igame.platform) else: - return self.game.app_version(self.core.default_platform) + return self.game.app_version(self.default_platform) @property def has_update(self) -> bool: From 54ac89a85a1816838ba86b9211d9e28dfb2c62ec Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 15:36:38 +0200 Subject: [PATCH 25/34] Lgndr: Validate default platform if it is coming from the config --- rare/lgndr/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rare/lgndr/core.py b/rare/lgndr/core.py index 68ce36de..c5954af3 100644 --- a/rare/lgndr/core.py +++ b/rare/lgndr/core.py @@ -55,7 +55,8 @@ class LegendaryCore(LegendaryCoreReal): @property def default_platform(self) -> str: os_default = "Mac" if sys_platform == "darwin" else "Windows" - return self.lgd.config.get("Legendary", "default_platform", fallback=os_default) + usr_platform = self.lgd.config.get("Legendary", "default_platform", fallback=os_default) + return usr_platform if usr_platform in ("Windows", "Win32", "Mac") else os_default # skip_sync defaults to false but since Rare is persistent, skip by default # def get_installed_game(self, app_name, skip_sync=True) -> InstalledGame: From bfa2335551dbbe758e98fe9160b69c11342baf5a Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 15:40:01 +0200 Subject: [PATCH 26/34] RareCore: Strengthen config initialization and checks * Check if the `default_platform` option exists and it is correct * Check if `install_dir` and `mac_install_dir` are already set --- rare/shared/rare_core.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index d1606af2..6bea7472 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -5,7 +5,7 @@ import time from argparse import Namespace from itertools import chain from logging import getLogger -from typing import Dict, Iterator, Callable, Optional, List, Union, Iterable, Tuple +from typing import Dict, Iterator, Callable, Optional, List, Union, Iterable, Tuple, Set from PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool, QRunnable, QTimer from legendary.lfs.eos import EOSOverlayApp @@ -143,18 +143,32 @@ class RareCore(QObject): with open(os.path.join(path, "config.ini"), "w") as config_file: config_file.write("[Legendary]") self.__core = LegendaryCore() + + # Initialize sections if they don't exist for section in ["Legendary", "default", "default.env"]: if section not in self.__core.lgd.config.sections(): self.__core.lgd.config.add_section(section) - # Set some platform defaults - self.__core.lgd.config.set( - "Legendary", "install_dir", self.__core.get_default_install_dir() - ) - self.__core.lgd.config.set( - "Legendary", "mac_install_dir", self.__core.get_default_install_dir(self.__core.default_platform) - ) - self.__core.lgd.config.set("Legendary", "default_platform", self.__core.default_platform) - self.__core.lgd.config.set("Legendary", "install_platform_fallback", False) + + # Set some platform defaults if unset + def check_config(option: str, accepted: Set = None) -> bool: + _exists = self.__core.lgd.config.has_option("Legendary", option) + _value = self.__core.lgd.config.get("Legendary", option, fallback="") + _accepted = _value in accepted if accepted is not None else True + return _exists and bool(_value) and _accepted + + if not check_config("default_platform", {"Windows", "Win32", "Mac"}): + self.__core.lgd.config.set("Legendary", "default_platform", self.__core.default_platform) + if not check_config("install_dir"): + self.__core.lgd.config.set( + "Legendary", "install_dir", self.__core.get_default_install_dir() + ) + if not check_config("mac_install_dir"): + self.__core.lgd.config.set( + "Legendary", "mac_install_dir", self.__core.get_default_install_dir(self.__core.default_platform) + ) + # Always set this to avoid falling back + self.__core.lgd.config.set("Legendary", "install_platform_fallback", 'false') + # workaround if egl sync enabled, but no programdata_path # programdata_path might be unset if logging in through the browser if self.__core.egl_sync_enabled: @@ -163,6 +177,7 @@ class RareCore(QObject): else: if not os.path.exists(self.__core.egl.programdata_path): self.__core.lgd.config.remove_option("Legendary", "egl_sync") + self.__core.lgd.save_config() return self.__core From b93435d920ebb03868e422dd99a8eb178a61daba Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 16:23:58 +0200 Subject: [PATCH 27/34] Lgndr: Disable legendary's update checks and ignore config file options --- rare/lgndr/core.py | 6 ++++++ rare/shared/rare_core.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rare/lgndr/core.py b/rare/lgndr/core.py index c5954af3..15ac8f53 100644 --- a/rare/lgndr/core.py +++ b/rare/lgndr/core.py @@ -58,6 +58,12 @@ class LegendaryCore(LegendaryCoreReal): usr_platform = self.lgd.config.get("Legendary", "default_platform", fallback=os_default) return usr_platform if usr_platform in ("Windows", "Win32", "Mac") else os_default + def update_check_enabled(self): + return False + + def update_notice_enabled(self): + return False + # skip_sync defaults to false but since Rare is persistent, skip by default # def get_installed_game(self, app_name, skip_sync=True) -> InstalledGame: # return super(LegendaryCore, self).get_installed_game(app_name, skip_sync) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index 6bea7472..f2a5d9c5 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -166,7 +166,8 @@ class RareCore(QObject): self.__core.lgd.config.set( "Legendary", "mac_install_dir", self.__core.get_default_install_dir(self.__core.default_platform) ) - # Always set this to avoid falling back + # Always set these options + # Avoid falling back to Windows games on macOS self.__core.lgd.config.set("Legendary", "install_platform_fallback", 'false') # workaround if egl sync enabled, but no programdata_path From 1cf53e3b540c879b38e4bb0b39f2d99753ed690e Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 20:26:56 +0200 Subject: [PATCH 28/34] Library: Filter unreal engine entries expect for the dedicated option --- rare/components/tabs/games/game_widgets/__init__.py | 10 +++++----- rare/shared/rare_core.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/rare/components/tabs/games/game_widgets/__init__.py b/rare/components/tabs/games/game_widgets/__init__.py index b1df4b3d..a5d5b58b 100644 --- a/rare/components/tabs/games/game_widgets/__init__.py +++ b/rare/components/tabs/games/game_widgets/__init__.py @@ -38,15 +38,15 @@ class LibraryWidgetController(QObject): elif "hidden" in widget.rgame.metadata.tags: visible = False elif filter_name == "installed": - visible = widget.rgame.is_installed + visible = widget.rgame.is_installed and not widget.rgame.is_unreal elif filter_name == "offline": - visible = widget.rgame.can_run_offline + visible = widget.rgame.can_run_offline and not widget.rgame.is_unreal elif filter_name == "32bit": - visible = widget.rgame.is_win32 + visible = widget.rgame.is_win32 and not widget.rgame.is_unreal elif filter_name == "mac": - visible = widget.rgame.is_mac + visible = widget.rgame.is_mac and not widget.rgame.is_unreal elif filter_name == "installable": - visible = not widget.rgame.is_non_asset + visible = not widget.rgame.is_non_asset and not widget.rgame.is_unreal elif filter_name == "include_ue": visible = True elif filter_name == "all": diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index f2a5d9c5..580a087b 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -1,6 +1,5 @@ import configparser import os -import platform import time from argparse import Namespace from itertools import chain From c9264c732e2f222cecdf533f9e150c8550fe4dc0 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sat, 16 Dec 2023 21:57:09 +0200 Subject: [PATCH 29/34] HeadBar: Default to `Mac games` fitler on macOS --- rare/components/tabs/games/__init__.py | 12 ++---- rare/components/tabs/games/head_bar.py | 57 ++++++++++---------------- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index 8b7044de..2cb3876d 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -34,8 +34,6 @@ class GamesTab(QStackedWidget): self.image_manager = ImageManagerSingleton() self.settings = QSettings() - self.active_filter: int = 0 - self.games_page = QWidget(parent=self) games_page_layout = QVBoxLayout(self.games_page) self.addWidget(self.games_page) @@ -99,10 +97,7 @@ class GamesTab(QStackedWidget): self.head_bar.refresh_list.clicked.connect(self.library_controller.update_list) self.head_bar.view.toggled.connect(self.toggle_view) - f = self.settings.value("filter", 0, int) - if f >= len(self.head_bar.available_filters): - f = 0 - self.active_filter = self.head_bar.available_filters[f] + self.active_filter: str = self.head_bar.filter.currentData(Qt.UserRole) # signals self.signals.game.installed.connect(self.update_count_games_label) @@ -158,7 +153,7 @@ class GamesTab(QStackedWidget): for rgame in self.rcore.games: icon_widget, list_widget = self.add_library_widget(rgame) if not icon_widget or not list_widget: - logger.warning(f"Excluding {rgame.app_name} from the game list") + logger.warning("Excluding %s from the game list", rgame.app_title) continue self.icon_view.layout().addWidget(icon_widget) self.list_view.layout().addWidget(list_widget) @@ -169,8 +164,7 @@ class GamesTab(QStackedWidget): try: icon_widget, list_widget = self.library_controller.add_game(rgame) except Exception as e: - raise e - logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}") + logger.error("Could not add widget for %s to library: %s", rgame.app_name, e) return None, None icon_widget.show_info.connect(self.show_game_info) list_widget.show_info.connect(self.show_game_info) diff --git a/rare/components/tabs/games/head_bar.py b/rare/components/tabs/games/head_bar.py index 1178108c..0ec67c5e 100644 --- a/rare/components/tabs/games/head_bar.py +++ b/rare/components/tabs/games/head_bar.py @@ -1,4 +1,6 @@ -from PyQt5.QtCore import QSettings, pyqtSignal, pyqtSlot +import platform as pf + +from PyQt5.QtCore import QSettings, pyqtSignal, pyqtSlot, Qt from PyQt5.QtWidgets import ( QLabel, QPushButton, @@ -22,45 +24,28 @@ class GameListHeadBar(QWidget): def __init__(self, parent=None): super(GameListHeadBar, self).__init__(parent=parent) self.rcore = RareCore.instance() - self.settings = QSettings() + self.settings = QSettings(self) - self.filter = QComboBox() - self.filter.addItems( - [ - self.tr("All games"), - self.tr("Installed only"), - self.tr("Offline Games"), - # self.tr("Hidden") - ] - ) - - self.available_filters = [ - "all", - "installed", - "offline", - # "hidden" - ] + self.filter = QComboBox(self) + self.filter.addItem(self.tr("All games"), "all") + self.filter.addItem(self.tr("Installed only"), "installed") + self.filter.addItem(self.tr("Offline Games"), "offline") + # self.filter.addItem(self.tr("Hidden"), "hidden") if self.rcore.bit32_games: - self.filter.addItem(self.tr("32 Bit Games")) - self.available_filters.append("32bit") - + self.filter.addItem(self.tr("32 Bit Games"), "32bit") if self.rcore.mac_games: - self.filter.addItem(self.tr("Mac games")) - self.available_filters.append("mac") - + self.filter.addItem(self.tr("Mac games"), "mac") if self.rcore.origin_games: - self.filter.addItem(self.tr("Exclude Origin")) - self.available_filters.append("installable") - - self.filter.addItem(self.tr("Include Unreal Engine")) - self.available_filters.append("include_ue") + self.filter.addItem(self.tr("Exclude Origin"), "installable") + self.filter.addItem(self.tr("Include Unreal Engine"), "include_ue") + filter_default = "mac" if pf.system() == "Darwin" else "all" + filter_index = i if (i := self.filter.findData(filter_default, Qt.UserRole)) >= 0 else 0 try: - self.filter.setCurrentIndex(self.settings.value("filter", 0, int)) + self.filter.setCurrentIndex(self.settings.value("library_filter", filter_index, int)) except TypeError: - self.settings.setValue("filter", 0) - self.filter.setCurrentIndex(0) - + self.settings.setValue("library_filter", filter_index) + self.filter.setCurrentIndex(filter_index) self.filter.currentIndexChanged.connect(self.filter_changed) integrations_menu = QMenu(self) @@ -139,6 +124,6 @@ class GameListHeadBar(QWidget): self.rcore.fetch() @pyqtSlot(int) - def filter_changed(self, i: int): - self.filterChanged.emit(self.available_filters[i]) - self.settings.setValue("filter", i) + def filter_changed(self, index: int): + self.filterChanged.emit(self.filter.itemData(index, Qt.UserRole)) + self.settings.setValue("library_filter", index) From dfa60aa99f59ab8e71024257d90d1497094b66b7 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 17 Dec 2023 01:20:07 +0200 Subject: [PATCH 30/34] ImportGroup: Add platform selection when importing games. If we are importing multiple games, default to importing the native platform if the game supports it, otherwise fallback to the Windows platform --- .../tabs/games/integrations/import_group.py | 44 +++++++---- .../tabs/games/integrations/import_group.py | 52 +++++++++---- .../tabs/games/integrations/import_group.ui | 75 +++++++++++++------ 3 files changed, 122 insertions(+), 49 deletions(-) diff --git a/rare/components/tabs/games/integrations/import_group.py b/rare/components/tabs/games/integrations/import_group.py index ae4eede6..f61f96d3 100644 --- a/rare/components/tabs/games/integrations/import_group.py +++ b/rare/components/tabs/games/integrations/import_group.py @@ -72,8 +72,10 @@ class ImportWorker(QRunnable): def __init__( self, - core: LegendaryCore, path: str, + core: LegendaryCore, + path: str, app_name: str = None, + platform: Optional[str] = None, import_folder: bool = False, import_dlcs: bool = False, import_force: bool = False @@ -86,6 +88,7 @@ class ImportWorker(QRunnable): self.path = Path(path) self.app_name = app_name self.import_folder = import_folder + self.platform = platform if platform is not None else self.core.default_platform self.import_dlcs = import_dlcs self.import_force = import_force @@ -110,9 +113,13 @@ class ImportWorker(QRunnable): result = ImportedGame(ImportResult.ERROR) result.path = str(path) if app_name or (app_name := find_app_name(str(path), self.core)): + game = self.core.get_game(app_name) result.app_name = app_name - result.app_title = self.core.get_game(app_name).app_title - success, message = self.__import_game(path, app_name) + result.app_title = game.app_title + platform = self.platform + if platform not in self.core.get_game(app_name, update_meta=False).asset_infos: + platform = "Windows" + success, message = self.__import_game(path, app_name, platform) if not success: result.result = ImportResult.FAILED result.message = message @@ -120,14 +127,9 @@ class ImportWorker(QRunnable): result.result = ImportResult.SUCCESS return result - def __import_game(self, path: Path, app_name: str): + def __import_game(self, path: Path, app_name: str, platform: str): cli = LegendaryCLI(self.core) status = LgndrIndirectStatus() - # FIXME: Add override option in import form - platform = self.core.default_platform - if platform not in self.core.get_game(app_name, update_meta=False).asset_infos: - platform = "Windows" - args = LgndrImportGameArgs( app_path=str(path), app_name=app_name, @@ -212,6 +214,7 @@ class ImportGroup(QGroupBox): self.app_name_edit = IndicatorLineEdit( placeholder=self.tr("Use in case the app name was not found automatically"), edit_func=self.app_name_edit_callback, + save_func=self.app_name_save_callback, parent=self, ) self.app_name_edit.textChanged.connect(self.app_name_changed) @@ -287,6 +290,12 @@ class ImportGroup(QGroupBox): else: return False, text, IndicatorReasonsCommon.NOT_INSTALLED + def app_name_save_callback(self, text) -> None: + rgame = self.rcore.get_game(text) + self.ui.platform_combo.clear() + self.ui.platform_combo.addItems(rgame.platforms) + self.ui.platform_combo.setCurrentText(rgame.default_platform) + @pyqtSlot(str) def app_name_changed(self, app_name: str): self.info_label.setText("") @@ -302,6 +311,14 @@ class ImportGroup(QGroupBox): @pyqtSlot(int) def import_folder_changed(self, state: Qt.CheckState): self.app_name_edit.setEnabled(not state) + self.ui.platform_combo.setEnabled(not state) + self.ui.platform_combo.setToolTip( + self.tr( + "When importing multiple games, the current OS will be used at the" + " platform for the games that support it, otherwise the Windows version" + " will be imported." + ) if state else "" + ) self.ui.import_dlcs_check.setCheckState(Qt.Unchecked) self.ui.import_force_check.setCheckState(Qt.Unchecked) self.ui.import_dlcs_check.setEnabled( @@ -330,10 +347,11 @@ class ImportGroup(QGroupBox): self.worker = ImportWorker( self.core, path, - self.app_name_edit.text(), - self.ui.import_folder_check.isChecked(), - self.ui.import_dlcs_check.isChecked(), - self.ui.import_force_check.isChecked() + app_name=self.app_name_edit.text(), + platform=self.ui.platform_combo.currentText() if not self.ui.import_folder_check.isChecked() else None, + import_folder=self.ui.import_folder_check.isChecked(), + import_dlcs=self.ui.import_dlcs_check.isChecked(), + import_force=self.ui.import_force_check.isChecked() ) self.worker.signals.progress.connect(self.__on_import_progress) self.worker.signals.result.connect(self.__on_import_result) diff --git a/rare/ui/components/tabs/games/integrations/import_group.py b/rare/ui/components/tabs/games/integrations/import_group.py index cc28fda1..ccd97153 100644 --- a/rare/ui/components/tabs/games/integrations/import_group.py +++ b/rare/ui/components/tabs/games/integrations/import_group.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ImportGroup(object): def setupUi(self, ImportGroup): ImportGroup.setObjectName("ImportGroup") - ImportGroup.resize(506, 184) + ImportGroup.resize(651, 218) ImportGroup.setWindowTitle("ImportGroup") ImportGroup.setWindowFilePath("") self.import_layout = QtWidgets.QFormLayout(ImportGroup) @@ -28,26 +28,35 @@ class Ui_ImportGroup(object): self.import_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.app_name_label) self.import_folder_label = QtWidgets.QLabel(ImportGroup) self.import_folder_label.setObjectName("import_folder_label") - self.import_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.import_folder_label) + self.import_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.import_folder_label) self.import_folder_check = QtWidgets.QCheckBox(ImportGroup) font = QtGui.QFont() font.setItalic(True) self.import_folder_check.setFont(font) self.import_folder_check.setObjectName("import_folder_check") - self.import_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.import_folder_check) + self.import_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.import_folder_check) self.import_dlcs_label = QtWidgets.QLabel(ImportGroup) self.import_dlcs_label.setObjectName("import_dlcs_label") - self.import_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.import_dlcs_label) + self.import_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.import_dlcs_label) self.import_dlcs_check = QtWidgets.QCheckBox(ImportGroup) font = QtGui.QFont() font.setItalic(True) self.import_dlcs_check.setFont(font) self.import_dlcs_check.setObjectName("import_dlcs_check") - self.import_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.import_dlcs_check) + self.import_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.import_dlcs_check) + self.import_force_label = QtWidgets.QLabel(ImportGroup) + self.import_force_label.setObjectName("import_force_label") + self.import_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.import_force_label) + self.import_force_check = QtWidgets.QCheckBox(ImportGroup) + font = QtGui.QFont() + font.setItalic(True) + self.import_force_check.setFont(font) + self.import_force_check.setObjectName("import_force_check") + self.import_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.import_force_check) self.import_button_label = QtWidgets.QLabel(ImportGroup) self.import_button_label.setText("Error") self.import_button_label.setObjectName("import_button_label") - self.import_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.import_button_label) + self.import_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.import_button_label) self.button_info_layout = QtWidgets.QHBoxLayout() self.button_info_layout.setObjectName("button_info_layout") self.import_button = QtWidgets.QPushButton(ImportGroup) @@ -58,16 +67,27 @@ class Ui_ImportGroup(object): self.import_button.setSizePolicy(sizePolicy) self.import_button.setObjectName("import_button") self.button_info_layout.addWidget(self.import_button) - self.import_layout.setLayout(5, QtWidgets.QFormLayout.FieldRole, self.button_info_layout) - self.import_force_label = QtWidgets.QLabel(ImportGroup) - self.import_force_label.setObjectName("import_force_label") - self.import_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.import_force_label) - self.import_force_check = QtWidgets.QCheckBox(ImportGroup) + self.import_layout.setLayout(6, QtWidgets.QFormLayout.FieldRole, self.button_info_layout) + self.platform_label = QtWidgets.QLabel(ImportGroup) + self.platform_label.setObjectName("platform_label") + self.import_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.platform_label) + self.platform_layout = QtWidgets.QHBoxLayout() + self.platform_layout.setObjectName("platform_layout") + self.platform_combo = QtWidgets.QComboBox(ImportGroup) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.platform_combo.sizePolicy().hasHeightForWidth()) + self.platform_combo.setSizePolicy(sizePolicy) + self.platform_combo.setObjectName("platform_combo") + self.platform_layout.addWidget(self.platform_combo) + self.platform_tooltip = QtWidgets.QLabel(ImportGroup) font = QtGui.QFont() font.setItalic(True) - self.import_force_check.setFont(font) - self.import_force_check.setObjectName("import_force_check") - self.import_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.import_force_check) + self.platform_tooltip.setFont(font) + self.platform_tooltip.setObjectName("platform_tooltip") + self.platform_layout.addWidget(self.platform_tooltip) + self.import_layout.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.platform_layout) self.retranslateUi(ImportGroup) @@ -80,9 +100,11 @@ class Ui_ImportGroup(object): self.import_folder_check.setText(_translate("ImportGroup", "Scan the installation path for game folders and import them")) self.import_dlcs_label.setText(_translate("ImportGroup", "Import DLCs")) self.import_dlcs_check.setText(_translate("ImportGroup", "If a game has DLCs, try to import them too")) - self.import_button.setText(_translate("ImportGroup", "Import Game")) self.import_force_label.setText(_translate("ImportGroup", "Force import")) self.import_force_check.setText(_translate("ImportGroup", "Import game despite missing files")) + self.import_button.setText(_translate("ImportGroup", "Import Game")) + self.platform_label.setText(_translate("ImportGroup", "Platform")) + self.platform_tooltip.setText(_translate("ImportGroup", "Select the native platform of the game")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/games/integrations/import_group.ui b/rare/ui/components/tabs/games/integrations/import_group.ui index 43ed7e5e..60dbd349 100644 --- a/rare/ui/components/tabs/games/integrations/import_group.ui +++ b/rare/ui/components/tabs/games/integrations/import_group.ui @@ -6,8 +6,8 @@ 0 0 - 506 - 184 + 651 + 218
@@ -37,14 +37,14 @@
- + Import all folders - + @@ -56,14 +56,14 @@ - + Import DLCs - + @@ -76,13 +76,32 @@ + + + Force import + + + + + + + + true + + + + Import game despite missing files + + + + Error - + @@ -99,24 +118,38 @@ - - + + - Force import + Platform - - - - - true - - - - Import game despite missing files - - + + + + + + + 0 + 0 + + + + + + + + + true + + + + Select the native platform of the game + + + + From 32565b3a9c3eb98d6ed2eb435b270463f43d0e6c Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 17 Dec 2023 01:28:56 +0200 Subject: [PATCH 31/34] HeadBar: Update some strings --- rare/components/tabs/games/head_bar.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rare/components/tabs/games/head_bar.py b/rare/components/tabs/games/head_bar.py index 0ec67c5e..2311d97c 100644 --- a/rare/components/tabs/games/head_bar.py +++ b/rare/components/tabs/games/head_bar.py @@ -28,16 +28,16 @@ class GameListHeadBar(QWidget): self.filter = QComboBox(self) self.filter.addItem(self.tr("All games"), "all") - self.filter.addItem(self.tr("Installed only"), "installed") - self.filter.addItem(self.tr("Offline Games"), "offline") + self.filter.addItem(self.tr("Installed"), "installed") + self.filter.addItem(self.tr("Offline"), "offline") # self.filter.addItem(self.tr("Hidden"), "hidden") if self.rcore.bit32_games: - self.filter.addItem(self.tr("32 Bit Games"), "32bit") + self.filter.addItem(self.tr("32bit games"), "32bit") if self.rcore.mac_games: - self.filter.addItem(self.tr("Mac games"), "mac") + self.filter.addItem(self.tr("macOS games"), "mac") if self.rcore.origin_games: self.filter.addItem(self.tr("Exclude Origin"), "installable") - self.filter.addItem(self.tr("Include Unreal Engine"), "include_ue") + self.filter.addItem(self.tr("Include Unreal"), "include_ue") filter_default = "mac" if pf.system() == "Darwin" else "all" filter_index = i if (i := self.filter.findData(filter_default, Qt.UserRole)) >= 0 else 0 From a990400e342be365bd9359ff6bab67cc341c92c9 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 17 Dec 2023 15:22:09 +0200 Subject: [PATCH 32/34] Rare: Release 1.10.9 --- AppImageBuilder.yml | 2 +- pyproject.toml | 2 +- rare/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index dcb6d67b..eb6cb1e3 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -20,7 +20,7 @@ AppDir: id: io.github.dummerle.rare name: Rare icon: Rare - version: 1.10.8 + version: 1.10.9 exec: usr/bin/python3 exec_args: $APPDIR/usr/src/rare/main.py $@ apt: diff --git a/pyproject.toml b/pyproject.toml index c21e48a5..779fe56c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ force-exclude = ''' [tool.poetry] name = "rare" -version = "1.10.8" +version = "1.10.9" description = "A GUI for Legendary" authors = ["Dummerle"] license = "GPL3" diff --git a/rare/__init__.py b/rare/__init__.py index 3de296ba..19e12745 100644 --- a/rare/__init__.py +++ b/rare/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.10.8" +__version__ = "1.10.9" __codename__ = "Garlic Crab" # For PyCharm profiler From bc485ed40bfb2fb59c8094581479473f65c0ed16 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 17 Dec 2023 19:34:08 +0200 Subject: [PATCH 33/34] RareCore: Don't delete overlay RareGame at exit --- rare/shared/rare_core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index 580a087b..5b7301af 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -206,7 +206,9 @@ class RareCore(QObject): del self.__args self.__args = None - del self.__eos_overlay + # del self.__eos_overlay + self.__eos_overlay = None + RareCore.__instance = None super(RareCore, self).deleteLater() From 85d29372d465743949cb5cb842c88d88e800baa9 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 17 Dec 2023 19:34:37 +0200 Subject: [PATCH 34/34] GameSettings: Hide proton settings on macOS --- rare/components/tabs/settings/game_settings.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rare/components/tabs/settings/game_settings.py b/rare/components/tabs/settings/game_settings.py index 201f5fee..d52dd79f 100644 --- a/rare/components/tabs/settings/game_settings.py +++ b/rare/components/tabs/settings/game_settings.py @@ -40,8 +40,10 @@ class DefaultGameSettings(QWidget): if platform.system() != "Windows": self.linux_settings = LinuxAppSettings() - self.proton_settings = ProtonSettings(self.linux_settings, self.wrapper_settings) - self.ui.proton_layout.addWidget(self.proton_settings) + if platform.system() != "Darwin": + self.proton_settings = ProtonSettings(self.linux_settings, self.wrapper_settings) + self.ui.proton_layout.addWidget(self.proton_settings) + self.proton_settings.environ_changed.connect(self.env_vars.reset_model) # FIXME: Remove the spacerItem and margins from the linux settings # FIXME: This should be handled differently at soem point in the future @@ -57,8 +59,6 @@ class DefaultGameSettings(QWidget): lambda active: self.wrapper_settings.add_wrapper("mangohud") if active else self.wrapper_settings.delete_wrapper("mangohud")) self.linux_settings.environ_changed.connect(self.env_vars.reset_model) - self.proton_settings.environ_changed.connect(self.env_vars.reset_model) - else: self.ui.linux_settings_widget.setVisible(False) @@ -77,7 +77,10 @@ class DefaultGameSettings(QWidget): proton = self.wrapper_settings.wrappers.get("proton", "") if proton: proton = proton.text - self.proton_settings.load_settings(app_name, proton) + if platform.system() != "Darwin": + self.proton_settings.load_settings(app_name, proton) + else: + proton = "" if proton: self.linux_settings.ui.wine_groupbox.setEnabled(False) else: