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