1
0
Fork 0
mirror of synced 2024-06-17 01:54:46 +12:00

Merge pull request #370 from loathingKernel/next

Refactor EOS overlay management form
This commit is contained in:
Stelios Tsampas 2024-01-22 00:22:01 +02:00 committed by GitHub
commit f9f9caf956
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 1085 additions and 916 deletions

View file

@ -22,13 +22,17 @@ on:
jobs: jobs:
pylint: 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: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: '3.9' python-version: ${{ matris.version }}
- name: Install dependencies - name: Install dependencies
run: | run: |
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip

View file

@ -12,7 +12,7 @@ from requests import HTTPError
from rare.components.dialogs.launch_dialog import LaunchDialog from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow from rare.components.main_window import MainWindow
from rare.shared import RareCore 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.utils.misc import ExitCodes
from rare.widgets.rare_app import RareApp, RareAppException from rare.widgets.rare_app import RareApp, RareAppException

View file

@ -6,8 +6,7 @@ from typing import Tuple, List, Union, Optional
from PyQt5.QtCore import QThreadPool, QSettings from PyQt5.QtCore import QThreadPool, QSettings
from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QShowEvent from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QVBoxLayout, QFormLayout from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QFormLayout
from legendary.utils.selective_dl import get_sdl_appname
from rare.models.game import RareGame from rare.models.game import RareGame
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel 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 import Ui_InstallDialog
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
from rare.utils.misc import format_size, icon 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.collapsible_widget import CollapsibleFrame
from rare.widgets.dialogs import ActionDialog, dialog_title_game
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
from rare.widgets.selective_widget import SelectiveWidget
class InstallDialogAdvanced(CollapsibleFrame): class InstallDialogAdvanced(CollapsibleFrame):
def __init__(self, parent=None): def __init__(self, parent=None):
widget = QWidget(parent) super(InstallDialogAdvanced, self).__init__(parent=parent)
title = widget.tr("Advanced options")
title = self.tr("Advanced options")
self.setTitle(title)
self.widget = QWidget(parent=self)
self.ui = Ui_InstallDialogAdvanced() self.ui = Ui_InstallDialogAdvanced()
self.ui.setupUi(widget) self.ui.setupUi(self.widget)
super(InstallDialogAdvanced, self).__init__(widget=widget, title=title, parent=parent) 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): class InstallDialog(ActionDialog):
@ -48,13 +75,12 @@ class InstallDialog(ActionDialog):
header = self.tr("Modify") header = self.tr("Modify")
bicon = icon("fa.gear") bicon = icon("fa.gear")
self.setWindowTitle(dialog_title_game(header, rgame.app_title)) self.setWindowTitle(dialog_title_game(header, rgame.app_title))
self.setSubtitle(dialog_title_game(header, rgame.app_title))
install_widget = QWidget(self) install_widget = QWidget(self)
self.ui = Ui_InstallDialog() self.ui = Ui_InstallDialog()
self.ui.setupUi(install_widget) self.ui.setupUi(install_widget)
self.ui.title_label.setText(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>")
self.core = rgame.core self.core = rgame.core
self.rgame = rgame self.rgame = rgame
self.__options: InstallOptionsModel = options self.__options: InstallOptionsModel = options
@ -64,7 +90,8 @@ class InstallDialog(ActionDialog):
self.advanced = InstallDialogAdvanced(parent=self) self.advanced = InstallDialogAdvanced(parent=self)
self.ui.advanced_layout.addWidget(self.advanced) 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.ui.selectable_layout.addWidget(self.selectable)
self.options_changed = False self.options_changed = False
@ -82,13 +109,14 @@ class InstallDialog(ActionDialog):
self.install_dir_edit = PathEdit( self.install_dir_edit = PathEdit(
path=base_path, path=base_path,
file_mode=QFileDialog.DirectoryOnly, file_mode=QFileDialog.DirectoryOnly,
edit_func=self.option_changed, edit_func=self.install_dir_edit_callback,
save_func=self.save_install_edit, save_func=self.install_dir_save_callback,
parent=self, parent=self,
) )
self.ui.main_layout.setWidget( self.ui.main_layout.setWidget(
self.ui.main_layout.getWidgetPosition(self.ui.install_dir_label)[0], 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) self.install_dir_edit.setDisabled(rgame.is_installed)
@ -102,10 +130,10 @@ class InstallDialog(ActionDialog):
self.ui.platform_combo.addItems(reversed(rgame.platforms)) self.ui.platform_combo.addItems(reversed(rgame.platforms))
combo_text = rgame.igame.platform if rgame.is_installed else rgame.default_platform 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.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.check_incompatible_platform)
self.ui.platform_combo.currentIndexChanged.connect(self.reset_install_dir) 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_label.setDisabled(rgame.is_installed)
self.ui.platform_combo.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") 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_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.check_incompatible_platform(self.ui.platform_combo.currentIndex())
self.accept_button.setEnabled(False) self.accept_button.setEnabled(False)
@ -180,7 +205,7 @@ class InstallDialog(ActionDialog):
def showEvent(self, a0: QShowEvent) -> None: def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous(): if a0.spontaneous():
return super().showEvent(a0) 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) super().showEvent(a0)
def execute(self): def execute(self):
@ -197,68 +222,31 @@ class InstallDialog(ActionDialog):
default_dir = self.core.get_default_install_dir(platform) default_dir = self.core.get_default_install_dir(platform)
self.install_dir_edit.setText(default_dir) 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) @pyqtSlot(int)
def check_incompatible_platform(self, index: int): def check_incompatible_platform(self, index: int):
platform = self.ui.platform_combo.itemText(index) platform = self.ui.platform_combo.itemText(index)
if platform == "Mac" and pf.system() != "Darwin": if platform == "Mac" and pf.system() != "Darwin":
self.error_box( self.error_box(
self.tr("Warning"), self.tr("Warning"),
self.tr("You will not be able to run the game if you select <b>{}</b> as platform").format(platform) self.tr("You will not be able to run the game if you select <b>{}</b> as platform").format(platform),
) )
else: else:
self.error_box() self.error_box()
def get_options(self): 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.max_workers = self.advanced.ui.max_workers_spin.value()
self.__options.shared_memory = self.advanced.ui.max_memory_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.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
self.__options.force = self.advanced.ui.force_download_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.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
self.__options.no_install = self.advanced.ui.download_only_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.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked() self.__options.install_tag = self.selectable.install_tags()
if self.selectable_checks: self.__options.reset_sdl = True
self.__options.install_tag = [""]
for cb in self.selectable_checks:
if data := cb.isChecked():
# noinspection PyTypeChecker
self.__options.install_tag.extend(data)
def get_download_info(self): def get_download_info(self):
self.__download = None self.__download = None
@ -279,13 +267,17 @@ class InstallDialog(ActionDialog):
self.get_options() self.get_options()
self.get_download_info() self.get_download_info()
def option_changed(self, path) -> Tuple[bool, str, int]: @pyqtSlot()
def option_changed(self):
self.options_changed = True self.options_changed = True
self.accept_button.setEnabled(False) self.accept_button.setEnabled(False)
self.action_button.setEnabled(not self.active()) 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 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): if not os.path.exists(path):
return return
_, _, free_space = shutil.disk_usage(path) _, _, free_space = shutil.disk_usage(path)
@ -369,12 +361,6 @@ class InstallDialog(ActionDialog):
self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download) self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download)
def reject_handler(self): 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) self.__queue_item = InstallQueueItemModel(options=self.__options, download=None)
@ -387,6 +373,3 @@ class TagCheckBox(QCheckBox):
def isChecked(self) -> Union[bool, List[str]]: def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False return self.tags if super(TagCheckBox, self).isChecked() else False

View file

@ -34,8 +34,7 @@ class MoveDialog(ActionDialog):
super(MoveDialog, self).__init__(parent=parent) super(MoveDialog, self).__init__(parent=parent)
header = self.tr("Move") header = self.tr("Move")
self.setWindowTitle(dialog_title_game(header, rgame.app_title)) self.setWindowTitle(dialog_title_game(header, rgame.app_title))
self.setSubtitle(dialog_title_game(header, rgame.app_title))
title_label = QLabel(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>", self)
self.rcore = RareCore.instance() self.rcore = RareCore.instance()
self.core = RareCore.instance().core() self.core = RareCore.instance().core()
@ -70,7 +69,6 @@ class MoveDialog(ActionDialog):
layout = QVBoxLayout() layout = QVBoxLayout()
layout.setSizeConstraint(QLayout.SetFixedSize) layout.setSizeConstraint(QLayout.SetFixedSize)
layout.addWidget(title_label)
layout.addWidget(self.path_edit) layout.addWidget(self.path_edit)
layout.addWidget(self.warn_label) layout.addWidget(self.warn_label)
layout.addLayout(bottom_layout) layout.addLayout(bottom_layout)

View file

@ -1,18 +1,11 @@
from typing import List, Union, Optional
from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLayout, QGroupBox
QLabel,
QVBoxLayout,
QCheckBox,
QLayout, QGroupBox,
)
from legendary.utils.selective_dl import get_sdl_appname
from rare.models.game import RareGame from rare.models.game import RareGame
from rare.models.install import SelectiveDownloadsModel from rare.models.install import SelectiveDownloadsModel
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
from rare.utils.misc import icon 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): class SelectiveDialog(ButtonDialog):
@ -22,23 +15,18 @@ class SelectiveDialog(ButtonDialog):
super(SelectiveDialog, self).__init__(parent=parent) super(SelectiveDialog, self).__init__(parent=parent)
header = self.tr("Optional downloads for") header = self.tr("Optional downloads for")
self.setWindowTitle(dialog_title_game(header, rgame.app_title)) self.setWindowTitle(dialog_title_game(header, rgame.app_title))
self.setSubtitle(dialog_title_game(header, rgame.app_title))
title_label = QLabel(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>", self)
self.core = rgame.core
self.rgame = rgame self.rgame = rgame
self.selective_widget = SelectiveWidget(rgame, rgame.igame.platform, self)
selectable_group = QGroupBox(self.tr("Optional downloads"), self) container = QGroupBox(self.tr("Optional downloads"), self)
self.selectable_layout = QVBoxLayout(selectable_group) container_layout = QVBoxLayout(container)
self.selectable_layout.setSpacing(0) container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.addWidget(self.selective_widget)
self.selectable_checks: List[TagCheckBox] = []
self.config_tags: Optional[List[str]] = None
layout = QVBoxLayout() layout = QVBoxLayout()
layout.setSizeConstraint(QLayout.SetFixedSize) layout.addWidget(container)
layout.addWidget(title_label)
layout.addWidget(selectable_group)
self.setCentralLayout(layout) self.setCentralLayout(layout)
@ -47,62 +35,13 @@ class SelectiveDialog(ButtonDialog):
self.options: SelectiveDownloadsModel = SelectiveDownloadsModel(rgame.app_name) 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): def done_handler(self):
self.result_ready.emit(self.rgame, self.options) self.result_ready.emit(self.rgame, self.options)
def accept_handler(self): 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.accepted = True
self.options.install_tag = install_tag self.options.install_tag = self.selective_widget.install_tags()
def reject_handler(self): def reject_handler(self):
self.options.accepted = False self.options.accepted = False
self.options.install_tag = None 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

View file

@ -1,6 +1,5 @@
from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QLabel,
QVBoxLayout, QVBoxLayout,
QCheckBox, QCheckBox,
) )
@ -18,19 +17,24 @@ class UninstallDialog(ButtonDialog):
super(UninstallDialog, self).__init__(parent=parent) super(UninstallDialog, self).__init__(parent=parent)
header = self.tr("Uninstall") header = self.tr("Uninstall")
self.setWindowTitle(dialog_title_game(header, rgame.app_title)) self.setWindowTitle(dialog_title_game(header, rgame.app_title))
self.setSubtitle(dialog_title_game(header, rgame.app_title))
title_label = QLabel(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>", self)
self.keep_files = QCheckBox(self.tr("Keep files")) self.keep_files = QCheckBox(self.tr("Keep files"))
self.keep_files.setChecked(bool(options.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 = QCheckBox(self.tr("Keep configuation"))
self.keep_config.setChecked(bool(options.keep_config)) 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 = QVBoxLayout()
layout.addWidget(title_label)
layout.addWidget(self.keep_files) layout.addWidget(self.keep_files)
layout.addWidget(self.keep_config) layout.addWidget(self.keep_config)
layout.addWidget(self.keep_overlay_keys)
self.setCentralLayout(layout) self.setCentralLayout(layout)
@ -51,7 +55,8 @@ class UninstallDialog(ButtonDialog):
True, True,
self.keep_files.isChecked(), self.keep_files.isChecked(),
self.keep_config.isChecked(), self.keep_config.isChecked(),
self.keep_overlay_keys.isChecked(),
) )
def reject_handler(self): def reject_handler(self):
self.options.values = (None, None, None) self.options.values = (None, None, None, None)

View file

@ -105,7 +105,9 @@ class DownloadsTab(QWidget):
def __add_update(self, update: Union[str, RareGame]): def __add_update(self, update: Union[str, RareGame]):
if isinstance(update, str): if isinstance(update, str):
update = self.rcore.get_game(update) 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( self.__get_install_options(
InstallOptionsModel(app_name=update.app_name, update=True, silent=True) InstallOptionsModel(app_name=update.app_name, update=True, silent=True)
) )
@ -192,7 +194,7 @@ class DownloadsTab(QWidget):
if item.expired: if item.expired:
self.__refresh_download(item) self.__refresh_download(item)
return 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.result.connect(self.__on_download_result)
dl_thread.progress.connect(self.__on_download_progress) dl_thread.progress.connect(self.__on_download_progress)
dl_thread.finished.connect(dl_thread.deleteLater) dl_thread.finished.connect(dl_thread.deleteLater)
@ -204,6 +206,11 @@ class DownloadsTab(QWidget):
RareCore.instance().image_manager().get_pixmap(rgame.app_name, True) 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) @pyqtSlot(UIUpdate, object)
def __on_download_progress(self, ui_update: UIUpdate, dl_size: int): def __on_download_progress(self, ui_update: UIUpdate, dl_size: int):
self.download_widget.ui.progress_bar.setValue(int(ui_update.progress)) 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 result.shortcut and desktop_links_supported():
if not create_desktop_link( if not create_desktop_link(
app_name=result.options.app_name, app_name=result.options.app_name,
app_title=result.shortcut_title, app_title=result.app_title,
link_name=result.shortcut_name, link_name=result.folder_name,
link_type="desktop", link_type="desktop",
): ):
# maybe add it to download summary, to show in finished downloads # maybe add it to download summary, to show in finished downloads
logger.error(f"Failed to create desktop link on {platform.system()}") logger.error(f"Failed to create desktop link on {platform.system()}")
else: 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.notify.emit(
self.signals.application.overlay_installed.emit() self.tr("Downloads"),
else: self.tr("Finished: \"{}\" is now playable.").format(result.app_title),
self.signals.application.notify.emit(result.options.app_name) )
if self.updates_group.contains(result.options.app_name): if self.updates_group.contains(result.options.app_name):
self.updates_group.set_widget_enabled(result.options.app_name, True) 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): if self.updates_group.contains(item.options.app_name):
self.updates_group.set_widget_enabled(item.options.app_name, True) self.updates_group.set_widget_enabled(item.options.app_name, True)
rgame.state = RareGame.State.IDLE rgame.state = RareGame.State.IDLE
self.update_queues_count()
@pyqtSlot(UninstallOptionsModel) @pyqtSlot(UninstallOptionsModel)
def __get_uninstall_options(self, options: UninstallOptionsModel): def __get_uninstall_options(self, options: UninstallOptionsModel):

View file

@ -34,8 +34,8 @@ class DlResultModel:
sync_saves: bool = False sync_saves: bool = False
tip_url: str = "" tip_url: str = ""
shortcut: bool = False shortcut: bool = False
shortcut_name: str = "" folder_name: str = ""
shortcut_title: str = "" app_title: str = ""
class DlThread(QThread): class DlThread(QThread):
@ -151,10 +151,9 @@ class DlThread(QThread):
self.item.download.repair_file, self.item.download.repair_file,
) )
if not self.item.options.update and self.item.options.create_shortcut: result.shortcut = not self.item.options.update and self.item.options.create_shortcut
result.shortcut = True result.folder_name = self.rgame.folder_name
result.shortcut_name = self.rgame.folder_name result.app_title = self.rgame.app_title
result.shortcut_title = self.rgame.app_title
self.__finish(result) self.__finish(result)

View file

@ -1,11 +1,11 @@
from typing import Optional from typing import Optional
from PyQt5.QtCore import Qt 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 rare.widgets.side_tab import SideTabWidget
from .egl_sync_group import EGLSyncGroup from .egl_sync_group import EGLSyncGroup
from .eos_group import EOSGroup from .eos_group import EosGroup
from .import_group import ImportGroup from .import_group import ImportGroup
from .ubisoft_group import UbisoftGroup from .ubisoft_group import UbisoftGroup
@ -34,8 +34,8 @@ class IntegrationsTabs(SideTabWidget):
self.tr(""), self.tr(""),
self, self,
) )
self.eos_group = EosGroup(self.eos_ubisoft)
self.ubisoft_group = UbisoftGroup(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.eos_group)
self.eos_ubisoft.addWidget(self.ubisoft_group) self.eos_ubisoft.addWidget(self.ubisoft_group)
self.eos_ubisoft_index = self.addTab(self.eos_ubisoft, self.tr("Epic Overlay and Ubisoft")) self.eos_ubisoft_index = self.addTab(self.eos_ubisoft, self.tr("Epic Overlay and Ubisoft"))

View file

@ -1,259 +1,280 @@
import os import os
import platform import platform
from logging import getLogger from logging import getLogger
from typing import List from typing import Optional
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, Qt, pyqtSlot, QSize
from PyQt5.QtWidgets import QGroupBox, QMessageBox from PyQt5.QtGui import QShowEvent
from legendary.lfs import eos from PyQt5.QtWidgets import (
QGroupBox,
QMessageBox,
QFrame,
QHBoxLayout,
QSizePolicy,
QLabel,
QPushButton,
QFormLayout,
QComboBox,
)
from rare.models.install import InstallOptionsModel from rare.lgndr.core import LegendaryCore
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton 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.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.utils.misc import icon
from rare.widgets.elide_label import ElideLabel
logger = getLogger("EpicOverlay") 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 CheckForUpdateWorker(QRunnable):
class CheckForUpdateSignals(QObject): class CheckForUpdateSignals(QObject):
update_available = pyqtSignal(bool) update_available = pyqtSignal(bool)
def __init__(self): def __init__(self, core: LegendaryCore):
super(CheckForUpdateWorker, self).__init__() super(CheckForUpdateWorker, self).__init__()
self.signals = self.CheckForUpdateSignals() self.signals = self.CheckForUpdateSignals()
self.setAutoDelete(True) self.setAutoDelete(True)
self.core = LegendaryCoreSingleton() self.core = core
def run(self) -> None: def run(self) -> None:
self.core.check_for_overlay_updates() self.core.check_for_overlay_updates()
self.signals.update_available.emit(self.core.overlay_update_available) 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"<i>{active_path}</i>")
self.path_select.clear()
if not self.overlay.is_installed and not self.overlay.available_paths(self.prefix):
self.setDisabled(True)
self.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): def __init__(self, parent=None):
super(EOSGroup, self).__init__(parent=parent) super(EosGroup, self).__init__(parent=parent)
self.ui = Ui_EosWidget() self.ui = Ui_EosWidget()
self.ui.setupUi(self) self.ui.setupUi(self)
# lk: set object names for CSS properties # lk: set object names for CSS properties
self.ui.install_button.setObjectName("InstallButton") self.ui.install_button.setObjectName("InstallButton")
self.ui.install_button.setIcon(icon("ri.install-line"))
self.ui.uninstall_button.setObjectName("UninstallButton") 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.ui.uninstall_button.setIcon(icon("ri.uninstall-line"))
self.core = LegendaryCoreSingleton() self.installed_path_label = ElideLabel(parent=self)
self.signals = GlobalSignalsSingleton() 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.uninstall_button.clicked.connect(self.uninstall_overlay)
self.ui.update_button.setVisible(False) if self.overlay.is_installed: # installed
self.overlay = self.core.lgd.get_overlay_install_info() self.installed_version_label.setText(f"<b>{self.overlay.version}</b>")
self.installed_path_label.setText(os.path.normpath(self.overlay.install_path))
self.signals.application.overlay_installed.connect(self.overlay_installation_finished) self.ui.overlay_stack.setCurrentWidget(self.ui.info_page)
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"<b>{self.overlay.version}</b>")
self.ui.installed_path_lbl.setText(f"<b>{self.overlay.install_path}</b>")
self.ui.overlay_stack.setCurrentIndex(0)
else: else:
self.ui.overlay_stack.setCurrentIndex(1) self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
self.ui.enable_frame.setDisabled(True) self.ui.update_button.setEnabled(False)
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.threadpool = QThreadPool.globalInstance() 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): def update_prefixes(self):
logger.debug("Updated prefixes") for widget in self.findChildren(EosPrefixWidget, options=Qt.FindDirectChildrenOnly):
pfxs = get_wine_prefixes() # returns /home/whatever widget.deleteLater()
self.ui.select_pfx_combo.clear()
for pfx in pfxs: if platform.system() != "Windows":
self.ui.select_pfx_combo.addItem(pfx.replace(os.path.expanduser("~/"), "~/")) 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: @pyqtSlot(bool)
self.ui.select_pfx_combo.setCurrentIndex( def check_for_update_finished(self, update_available: bool):
self.ui.select_pfx_combo.findText(self.current_prefix.replace(os.path.expanduser("~/"), "~/"))) self.worker = None
self.ui.update_button.setEnabled(update_available)
def check_for_update(self): def check_for_update(self):
def worker_finished(update_available): self.ui.update_button.setEnabled(False)
self.ui.update_button.setVisible(update_available) if not self.overlay.is_installed:
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"))
return return
self.ui.overlay_stack.setCurrentIndex(0) if self.worker is not None:
self.ui.installed_version_lbl.setText(f"<b>{self.overlay.version}</b>")
self.ui.installed_path_lbl.setText(f"<b>{self.overlay.install_path}</b>")
self.ui.update_button.setVisible(False)
self.ui.enable_frame.setEnabled(True)
def update_select_combo(self, i: None):
if i is None:
i = self.ui.select_pfx_combo.currentIndex()
prefix = os.path.expanduser(self.ui.select_pfx_combo.itemText(i))
if platform.system() != "Windows" and not os.path.isfile(os.path.join(prefix, "user.reg")):
return return
self.current_prefix = prefix
reg_paths = eos.query_registry_entries(self.current_prefix)
overlay_enabled = False self.worker = CheckForUpdateWorker(self.core)
if reg_paths['overlay_path'] and self.core.is_overlay_install(reg_paths['overlay_path']): self.worker.signals.update_available.connect(self.check_for_update_finished)
overlay_enabled = True QThreadPool.globalInstance().start(self.worker)
self.ui.enabled_cb.setChecked(overlay_enabled)
def change_enable(self): @pyqtSlot()
enabled = self.ui.enabled_cb.isChecked() def install_finished(self):
if not enabled: if not self.overlay.is_installed:
try: logger.error("Something went wrong while installing overlay")
eos.remove_registry_entries(self.current_prefix) QMessageBox.warning(self, "Error", self.tr("Something went wrong while installing Overlay"))
except PermissionError: return
logger.error("Can't disable eos overlay") self.ui.overlay_stack.setCurrentWidget(self.ui.info_page)
QMessageBox.warning(self, "Error", self.tr( self.installed_version_label.setText(f"<b>{self.overlay.version}</b>")
"Failed to disable Overlay. Probably it is installed by Epic Games Launcher")) self.installed_path_label.setText(self.overlay.install_path)
return self.ui.update_button.setEnabled(False)
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
if not self.core.is_overlay_install(path): @pyqtSlot()
logger.error(f'Not a valid Overlay installation: {path}') def uninstall_finished(self):
self.ui.select_pfx_combo.removeItem(self.ui.select_pfx_combo.currentIndex()) self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
return
path = os.path.normpath(path) @pyqtSlot()
reg_paths = eos.query_registry_entries(self.current_prefix) def install_overlay(self):
if old_path := reg_paths["overlay_path"]: self.overlay.install()
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)
def uninstall_overlay(self): def uninstall_overlay(self):
if not self.core.is_overlay_installed(): if not self.overlay.is_installed:
logger.error('No legendary-managed overlay installation found.') logger.error("No Legendary-managed overlay installation found.")
self.ui.overlay_stack.setCurrentIndex(1) self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
return return
self.overlay.uninstall()
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)

View file

@ -8,17 +8,23 @@ class DebugSettings(QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super(DebugSettings, self).__init__(parent=parent) 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.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( self.restart_button.clicked.connect(
lambda: GlobalSignalsSingleton().application.quit.emit(ExitCodes.LOGOUT) 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 = QVBoxLayout(self)
layout.addWidget(self.raise_runtime_exception_button) layout.addWidget(self.raise_runtime_exception_button)
layout.addWidget(self.restart_button) layout.addWidget(self.restart_button)
layout.addWidget(self.send_notification_button)
layout.addStretch(1) layout.addStretch(1)
def raise_exception(self): def raise_exception(self):
raise RuntimeError("Debug Crash") raise RuntimeError("Debug Crash")
def send_notification(self):
GlobalSignalsSingleton().application.notify.emit("Debug", "Test notification")

View file

@ -116,6 +116,19 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked()) 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) self.refresh_metadata_button.clicked.connect(self.refresh_metadata)
# FIXME: Disable the button for now because it interferes with RareCore # FIXME: Disable the button for now because it interferes with RareCore
self.refresh_metadata_button.setEnabled(False) self.refresh_metadata_button.setEnabled(False)

View file

@ -59,17 +59,10 @@ class TrayIcon(QSystemTrayIcon):
last_played.sort(key=lambda g: g.metadata.last_played, reverse=True) last_played.sort(key=lambda g: g.metadata.last_played, reverse=True)
return last_played[0:5] return last_played[0:5]
@pyqtSlot(str) @pyqtSlot(str, str)
def notify(self, app_name: str): def notify(self, title: str, body: str):
if self.settings.value("notification", True, bool): if self.settings.value("notification", True, bool):
self.showMessage( self.showMessage(f"{QApplication.applicationName()} - {title}", body, QSystemTrayIcon.Information, 4000)
self.tr("Download finished"),
self.tr("Download finished. {} is playable now").format(
self.rcore.get_game(app_name).app_title
),
self.Information,
4000,
)
@pyqtSlot() @pyqtSlot()
def update_actions(self): def update_actions(self):

View file

@ -105,9 +105,15 @@ def get_game_params(rgame: RareGameSlim, args: InitArgs, launch_args: LaunchArgs
app_name = rgame.game.metadata['mainGameItem']['releaseInfo'][0]['appId'] app_name = rgame.game.metadata['mainGameItem']['releaseInfo'][0]['appId']
rgame.igame = rgame.core.get_installed_game(app_name) rgame.igame = rgame.core.get_installed_game(app_name)
params: LaunchParameters = rgame.core.get_launch_parameters( try:
app_name=rgame.game.app_name, offline=args.offline, addon_app_name=rgame.igame.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
)
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 = [] full_params = []
launch_args.environment = QProcessEnvironment.systemEnvironment() launch_args.environment = QProcessEnvironment.systemEnvironment()

View file

@ -341,7 +341,7 @@ class LegendaryCLI(LegendaryCLIReal):
self.core.uninstall_tag(old_igame) self.core.uninstall_tag(old_igame)
self.core.install_game(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.config.set(game.app_name, 'install_tags', ','.join(old_igame.install_tags))
self.core.lgd.save_config() self.core.lgd.save_config()

View file

@ -8,6 +8,7 @@ from typing import Optional, List, Tuple
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, QThreadPool, QSettings from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, QThreadPool, QSettings
from legendary.lfs import eos from legendary.lfs import eos
from legendary.models.game import SaveGameFile, SaveGameStatus, Game, InstalledGame 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.lgndr.core import LegendaryCore
from rare.models.install import UninstallOptionsModel, InstallOptionsModel from rare.models.install import UninstallOptionsModel, InstallOptionsModel
@ -178,6 +179,10 @@ class RareGameBase(QObject):
except AttributeError: except AttributeError:
return False return False
@property
def sdl_name(self) -> Optional[str]:
return get_sdl_appname(self.app_name)
@property @property
def version(self) -> str: def version(self) -> str:
"""! """!
@ -268,7 +273,7 @@ class RareGameSlim(RareGameBase):
return return
if thread: if thread:
worker = QRunnable.create(lambda: _upload()) worker = QRunnable.create(_upload)
QThreadPool.globalInstance().start(worker) QThreadPool.globalInstance().start(worker)
else: else:
_upload() _upload()
@ -293,7 +298,7 @@ class RareGameSlim(RareGameBase):
return return
if thread: if thread:
worker = QRunnable.create(lambda: _download()) worker = QRunnable.create(_download)
QThreadPool.globalInstance().start(worker) QThreadPool.globalInstance().start(worker)
else: else:
_download() _download()

View file

@ -8,13 +8,13 @@ from threading import Lock
from typing import List, Optional, Dict, Set from typing import List, Optional, Dict, Set
from PyQt5.QtCore import QRunnable, pyqtSlot, QProcess, QThreadPool 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.models.game import Game, InstalledGame
from legendary.utils.selective_dl import get_sdl_appname
from rare.lgndr.core import LegendaryCore from rare.lgndr.core import LegendaryCore
from rare.models.install import InstallOptionsModel, UninstallOptionsModel
from rare.models.base_game import RareGameBase, RareGameSlim 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.game_process import GameProcess
from rare.shared.image_manager import ImageManager from rare.shared.image_manager import ImageManager
from rare.utils.paths import data_dir, get_rare_executable from rare.utils.paths import data_dir, get_rare_executable
@ -412,10 +412,6 @@ class RareGame(RareGameSlim):
else self.app_title else self.app_title
) )
@property
def sdl_name(self) -> Optional[str]:
return get_sdl_appname(self.app_name)
@property @property
def save_path(self) -> Optional[str]: def save_path(self) -> Optional[str]:
return super(RareGame, self).save_path return super(RareGame, self).save_path
@ -433,9 +429,12 @@ class RareGame(RareGameSlim):
elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date) elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date)
if self.metadata.steam_grade is not None and elapsed_time.days < 3: if self.metadata.steam_grade is not None and elapsed_time.days < 3:
return self.metadata.steam_grade 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) QThreadPool.globalInstance().start(worker)
return "pending" return "pending"
@ -446,9 +445,10 @@ class RareGame(RareGameSlim):
self.signals.widget.update.emit() self.signals.widget.update.emit()
def grant_date(self, force=False) -> datetime: 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: if self.metadata.grant_date is None or force:
logger.debug("Grant date for %s not found in metadata, resolving", self.app_name) 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) matching = filter(lambda ent: ent["namespace"] == self.game.namespace, entitlements)
entitlement = next(matching, None) entitlement = next(matching, None)
grant_date = datetime.fromisoformat( grant_date = datetime.fromisoformat(
@ -481,6 +481,7 @@ class RareGame(RareGameSlim):
def set_pixmap(self): def set_pixmap(self):
self.pixmap = self.image_manager.get_pixmap(self.app_name, self.is_installed) self.pixmap = self.image_manager.get_pixmap(self.app_name, self.is_installed)
QPixmapCache.clear()
if not self.pixmap.isNull(): if not self.pixmap.isNull():
self.signals.widget.update.emit() self.signals.widget.update.emit()
@ -572,3 +573,93 @@ class RareEosOverlay(RareGameBase):
else: else:
self.igame = None self.igame = None
self.signals.game.uninstalled.emit(self.app_name) 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

View file

@ -58,10 +58,10 @@ class ImageSize:
def base(self) -> 'ImageSize.Preset': def base(self) -> 'ImageSize.Preset':
return self.__base return self.__base
Image = Preset(1, 2) Image = Preset(1, 1)
"""! @brief Size and pixel ratio of the image on disk""" """! @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""" """! @brief Size and pixel ratio for wide 16/9 image on disk"""
Display = Preset(1, 1, base=Image) Display = Preset(1, 1, base=Image)

View file

@ -86,6 +86,7 @@ class UninstallOptionsModel:
accepted: bool = None accepted: bool = None
keep_files: bool = None keep_files: bool = None
keep_config: bool = None keep_config: bool = None
keep_overlay_keys: bool = None
def __bool__(self): def __bool__(self):
return ( return (
@ -93,20 +94,21 @@ class UninstallOptionsModel:
and (self.accepted is not None) and (self.accepted is not None)
and (self.keep_files is not None) and (self.keep_files is not None)
and (self.keep_config is not None) and (self.keep_config is not None)
and (self.keep_overlay_keys is not None)
) )
@property @property
def values(self) -> Tuple[bool, bool, bool]: def values(self) -> Tuple[bool, bool, bool, bool]:
""" """
This model's options This model's options
:return: :return:
Tuple of `accepted` `keep_files` `keep_config` `keep_overlay_keys` 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 @values.setter
def values(self, values: Tuple[bool, bool, bool]): def values(self, values: Tuple[bool, bool, bool, bool]):
""" """
Set this model's options Set this model's options
@ -117,6 +119,7 @@ class UninstallOptionsModel:
self.accepted = values[0] self.accepted = values[0]
self.keep_files = values[1] self.keep_files = values[1]
self.keep_config = values[2] self.keep_config = values[2]
self.keep_overlay_keys = values[3]
@dataclass @dataclass

View file

@ -10,14 +10,12 @@ class GlobalSignals:
class ApplicationSignals(QObject): class ApplicationSignals(QObject):
# int: exit code # int: exit code
quit = pyqtSignal(int) quit = pyqtSignal(int)
# str: app_title # str: title, str: body
notify = pyqtSignal(str) notify = pyqtSignal(str, str)
# none # none
prefix_updated = pyqtSignal() prefix_updated = pyqtSignal()
# none # none
overlay_installed = pyqtSignal()
# none
update_tray = pyqtSignal() update_tray = pyqtSignal()
# none # none
update_statusbar = pyqtSignal() update_statusbar = pyqtSignal()
@ -58,4 +56,4 @@ class GlobalSignals:
self.download.deleteLater() self.download.deleteLater()
del self.download del self.download
self.discord_rpc.deleteLater() self.discord_rpc.deleteLater()
del self.discord_rpc del self.discord_rpc

View file

@ -64,7 +64,14 @@ class GameProcess(QObject):
self.tried_connections += 1 self.tried_connections += 1
if self.tried_connections > 50: # 10 seconds 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 <b>{}</b> 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.timer.stop()
self.finished.emit(GameProcess.Code.TIMEOUT) self.finished.emit(GameProcess.Code.TIMEOUT)

View file

@ -82,7 +82,7 @@ class ImageManager(QObject):
self.device = ImageSize.Preset(1, QApplication.instance().devicePixelRatio()) self.device = ImageSize.Preset(1, QApplication.instance().devicePixelRatio())
self.threadpool = QThreadPool() self.threadpool = QThreadPool()
self.threadpool.setMaxThreadCount(6) self.threadpool.setMaxThreadCount(4)
def __img_dir(self, app_name: str) -> Path: def __img_dir(self, app_name: str) -> Path:
return self.image_dir.joinpath(app_name) return self.image_dir.joinpath(app_name)

View file

@ -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 PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool, QRunnable, QTimer
from legendary.lfs.eos import EOSOverlayApp from legendary.lfs.eos import EOSOverlayApp
from legendary.models.game import Game, SaveGameFile 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.lgndr.core import LegendaryCore
from rare.models.base_game import RareSaveGame from rare.models.base_game import RareSaveGame
from rare.models.game import RareGame, RareEosOverlay from rare.models.game import RareGame, RareEosOverlay
from rare.models.signals import GlobalSignals from rare.models.signals import GlobalSignals
from rare.utils.metrics import timelogger
from rare.utils import config_helper
from .image_manager import ImageManager from .image_manager import ImageManager
from .workers import ( from .workers import (
QueueWorker, QueueWorker,
VerifyWorker, VerifyWorker,
MoveWorker, MoveWorker,
FetchWorker, FetchWorker,
GamesDlcsWorker,
EntitlementsWorker,
OriginWineWorker, OriginWineWorker,
) )
from .workers.uninstall import uninstall_game from .workers.uninstall import uninstall_game
@ -50,7 +54,7 @@ class RareCore(QObject):
self.__core: Optional[LegendaryCore] = None self.__core: Optional[LegendaryCore] = None
self.__image_manager: Optional[ImageManager] = None self.__image_manager: Optional[ImageManager] = None
self.__start_time = time.time() self.__start_time = time.perf_counter()
self.args(args) self.args(args)
self.signals(init=True) self.signals(init=True)
@ -66,6 +70,12 @@ class RareCore(QObject):
self.__library: Dict[str, RareGame] = {} self.__library: Dict[str, RareGame] = {}
self.__eos_overlay = RareEosOverlay(self.__core, EOSOverlayApp) 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 RareCore.__instance = self
@ -224,12 +234,12 @@ class RareCore(QObject):
for dlc in rgame.owned_dlcs: for dlc in rgame.owned_dlcs:
if dlc.is_installed: if dlc.is_installed:
logger.info(f'Uninstalling DLC "{dlc.app_name}" ({dlc.app_title})...') 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 dlc.igame = None
logger.info( logger.info(
f'Removing "{rgame.app_title}" because "{rgame.igame.install_path}" does not exist...' 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") logger.info(f"Uninstalled {rgame.app_title}, because no game files exist")
rgame.igame = None rgame.igame = None
return return
@ -254,6 +264,9 @@ class RareCore(QObject):
return self.__eos_overlay return self.__eos_overlay
return self.__library[app_name] return self.__library[app_name]
def get_overlay(self):
return self.get_game(EOSOverlayApp.app_name)
def __add_game(self, rgame: RareGame) -> None: def __add_game(self, rgame: RareGame) -> None:
rgame.signals.download.enqueue.connect(self.__signals.download.enqueue) rgame.signals.download.enqueue.connect(self.__signals.download.enqueue)
rgame.signals.download.dequeue.connect(self.__signals.download.dequeue) 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.info(f'Marking "{rgame.app_title}" as not installed because an exception has occurred...')
logger.error(e) logger.error(e)
rgame.set_installed(False) rgame.set_installed(False)
self.progress.emit(int(idx/length * 80) + 20, self.tr("Loaded <b>{}</b>").format(rgame.app_title)) progress = int(idx/length * self.__fetch_progress) + (100 - self.__fetch_progress)
self.progress.emit(progress, self.tr("Loaded <b>{}</b>").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) @pyqtSlot(object, int)
def __on_fetch_result(self, result: Tuple[List, Dict], res_type: int): def __on_fetch_result(self, result: Tuple, result_type: int):
logger.info(f"Got API results for {FetchWorker.Result(res_type).name}") if result_type == FetchWorker.Result.GAMESDLCS:
self.progress.emit(15, self.tr("Preparing library")) self.__add_games_and_dlcs(*result)
self.__add_games_and_dlcs(*result) self.__fetched_games_dlcs = True
self.progress.emit(100, self.tr("Launching Rare"))
logger.debug(f"Fetch time {time.time() - self.__start_time} seconds") if result_type == FetchWorker.Result.ENTITLEMENTS:
QTimer.singleShot(100, self.__post_init) self.__core.lgd.entitlements = result
self.completed.emit() 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): def fetch(self):
self.__start_time = time.time() self.__start_time = time.perf_counter()
fetch_worker = FetchWorker(self.__core, self.__args)
fetch_worker.signals.progress.connect(self.progress) games_dlcs_worker = GamesDlcsWorker(self.__core, self.__args)
fetch_worker.signals.result.connect(self.__on_fetch_result) games_dlcs_worker.signals.progress.connect(self.__on_fetch_progress)
QThreadPool.globalInstance().start(fetch_worker) 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_saves(self):
def __fetch() -> None: def __fetch() -> None:
start_time = time.time()
saves_dict: Dict[str, List[SaveGameFile]] = {} saves_dict: Dict[str, List[SaveGameFile]] = {}
try: 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: for s in saves_list:
if s.app_name not in saves_dict.keys(): if s.app_name not in saves_dict.keys():
saves_dict[s.app_name] = [s] saves_dict[s.app_name] = [s]
@ -332,31 +366,14 @@ class RareCore(QObject):
continue continue
self.__library[app_name].load_saves(saves) self.__library[app_name].load_saves(saves)
except (HTTPError, ConnectionError) as e: 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 return
logger.debug(f"Saves: {len(saves_dict)}") logger.info(f"Saves: {len(saves_dict)}")
logger.debug(f"Request saves: {time.time() - start_time} seconds")
saves_worker = QRunnable.create(__fetch) saves_worker = QRunnable.create(__fetch)
QThreadPool.globalInstance().start(saves_worker) 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: def resolve_origin(self) -> None:
origin_worker = OriginWineWorker(self.__core, list(self.origin_games)) origin_worker = OriginWineWorker(self.__core, list(self.origin_games))
QThreadPool.globalInstance().start(origin_worker) QThreadPool.globalInstance().start(origin_worker)
@ -364,7 +381,6 @@ class RareCore(QObject):
def __post_init(self) -> None: def __post_init(self) -> None:
if not self.__args.offline: if not self.__args.offline:
self.fetch_saves() self.fetch_saves()
self.fetch_entitlements()
self.resolve_origin() self.resolve_origin()
@property @property

View file

@ -1,4 +1,4 @@
from .fetch import FetchWorker from .fetch import FetchWorker, GamesDlcsWorker, EntitlementsWorker
from .install_info import InstallInfoWorker from .install_info import InstallInfoWorker
from .move import MoveWorker from .move import MoveWorker
from .uninstall import UninstallWorker from .uninstall import UninstallWorker

View file

@ -1,13 +1,13 @@
import platform import platform
import time
from argparse import Namespace from argparse import Namespace
from enum import IntEnum from enum import IntEnum
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import QObject, pyqtSignal, QSettings 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.lgndr.core import LegendaryCore
from rare.utils.metrics import timelogger
from .worker import Worker from .worker import Worker
logger = getLogger("FetchWorker") logger = getLogger("FetchWorker")
@ -15,9 +15,9 @@ logger = getLogger("FetchWorker")
class FetchWorker(Worker): class FetchWorker(Worker):
class Result(IntEnum): class Result(IntEnum):
GAMES = 1 ERROR = 0
NON_ASSET = 2 GAMESDLCS = 1
COMBINED = 3 ENTITLEMENTS = 2
class Signals(QObject): class Signals(QObject):
progress = pyqtSignal(int, str) progress = pyqtSignal(int, str)
@ -30,15 +30,39 @@ class FetchWorker(Worker):
self.args = args self.args = args
self.settings = QSettings() 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_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug
want_win32 = self.settings.value("win32_meta", False, bool) want_win32 = self.settings.value("win32_meta", False, bool)
want_macos = self.settings.value("macos_meta", False, bool) want_macos = self.settings.value("macos_meta", False, bool)
need_macos = platform.system() == "Darwin" 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: if want_win32 or self.args.debug:
logger.info( logger.info(
@ -47,9 +71,10 @@ class FetchWorker(Worker):
"with" if want_unreal else "without" "with" if want_unreal else "without"
) )
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows")) self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows"))
self.core.get_game_and_dlc_list( with timelogger(logger, "Request Win32 games"):
update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal 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: if need_macos or want_macos or self.args.debug:
logger.info( logger.info(
@ -58,9 +83,10 @@ class FetchWorker(Worker):
"with" if want_unreal else "without" "with" if want_unreal else "without"
) )
self.signals.progress.emit(15, self.signals.tr("Updating game metadata for macOS")) self.signals.progress.emit(15, self.signals.tr("Updating game metadata for macOS"))
self.core.get_game_and_dlc_list( with timelogger(logger, "Request macOS games"):
update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal self.core.get_game_and_dlc_list(
) update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal
)
if need_windows: if need_windows:
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for 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", "Requesting Windows metadata, %s Unreal engine",
"with" if want_unreal else "without" "with" if want_unreal else "without"
) )
games, dlc_dict = self.core.get_game_and_dlc_list( with timelogger(logger, "Request Windows games"):
update_assets=need_windows, platform="Windows", skip_ue=not want_unreal 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") logger.info(f"Games: %s. Games with DLCs: %s", len(games), len(dlc_dict))
# Fetch non-asset games # Fetch non-asset games
self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata")) want_non_asset = not self.settings.value("exclude_non_asset", False, bool)
start_time = time.time() if want_non_asset:
try: self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False) try:
except (HTTPError, ConnectionError) as e: with timelogger(logger, "Request non-asset"):
logger.warning(f"Exception while fetching non asset games from EGS: {e}") na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
na_games, na_dlc_dict = ([], {}) except (HTTPError, ConnectionError) as e:
# FIXME: logger.error(f"Network error while fetching non asset games")
# This is here because of broken appIds from Epic: logger.error(e)
# https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729 na_games, na_dlc_dict = ([], {})
# There is a tab character in the appId of Fallout New Vegas: Honest Hearts DLC, this breaks metadata storage # NOTE: This is here because of broken appIds from Epic
# on Windows as they can't handle tabs at the end of the filename (?) # https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729
# Legendary and Heroic are also affected, but it completely breaks Rare, so dodge it for now pending a fix. except Exception as e:
except Exception as e: logger.error("General exception while fetching non asset games from EGS.")
logger.error(f"Exception while fetching non asset games from EGS: {e}") logger.error(e)
na_games, na_dlc_dict = ([], {}) na_games, na_dlc_dict = ([], {})
logger.debug(f"Non-asset {len(na_games)}, games with non-asset DLCs {len(na_dlc_dict)}") logger.info("Non-asset: %s. Non-asset with DLCs: %s", len(na_games), len(na_dlc_dict))
logger.debug(f"Request non-asset: {time.time() - start_time} seconds")
# Combine the two games lists and the two dlc dictionaries between regular and non-asset results # Combine the two games lists and the two dlc dictionaries between regular and non-asset results
games += na_games games += na_games
for catalog_id, dlcs in na_dlc_dict.items(): for catalog_id, dlcs in na_dlc_dict.items():
if catalog_id in dlc_dict.keys(): if catalog_id in dlc_dict.keys():
dlc_dict[catalog_id] += dlcs dlc_dict[catalog_id] += dlcs
else: else:
dlc_dict[catalog_id] = dlcs dlc_dict[catalog_id] = dlcs
logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}") 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)

View file

@ -41,9 +41,6 @@ class InstallInfoWorker(Worker):
else: else:
raise LgndrException(status.message) raise LgndrException(status.message)
else: else:
if not os.path.exists(path := self.options.base_path):
os.makedirs(path)
dlm, analysis, igame = self.core.prepare_overlay_install( dlm, analysis, igame = self.core.prepare_overlay_install(
path=self.options.base_path path=self.options.base_path
) )

View file

@ -1,7 +1,10 @@
import platform
from logging import getLogger from logging import getLogger
from typing import Tuple
from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtCore import QObject, pyqtSignal
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
from legendary.lfs.eos import remove_registry_entries
from rare.lgndr.cli import LegendaryCLI from rare.lgndr.cli import LegendaryCLI
from rare.lgndr.glue.arguments import LgndrUninstallGameArgs 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 # 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): def uninstall_game(
game = core.get_game(app_name) 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 # remove shortcuts link
if desktop_links_supported(): if desktop_links_supported():
for link_type in desktop_link_types(): for link_type in desktop_link_types():
link_path = desktop_link_path( 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(): if link_path.exists():
link_path.unlink(missing_ok=True) 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() status = LgndrIndirectStatus()
LegendaryCLI(core).uninstall_game( LegendaryCLI(core).uninstall_game(
LgndrUninstallGameArgs( LgndrUninstallGameArgs(
app_name=app_name, app_name=rgame.app_name,
keep_files=keep_files, keep_files=keep_files,
skip_uninstaller=False, skip_uninstaller=False,
yes=True, yes=True,
@ -40,8 +65,8 @@ def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_co
) )
if not keep_config: if not keep_config:
logger.info("Removing sections in config file") logger.info("Removing sections in config file")
config_helper.remove_section(app_name) config_helper.remove_section(rgame.app_name)
config_helper.remove_section(f"{app_name}.env") config_helper.remove_section(f"{rgame.app_name}.env")
config_helper.save_config() config_helper.save_config()
@ -62,7 +87,11 @@ class UninstallWorker(Worker):
def run_real(self) -> None: def run_real(self) -> None:
self.rgame.state = RareGame.State.UNINSTALLING self.rgame.state = RareGame.State.UNINSTALLING
success, message = uninstall_game( 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.rgame.state = RareGame.State.IDLE
self.signals.result.emit(self.rgame, success, message) self.signals.result.emit(self.rgame, success, message)

View file

@ -14,20 +14,17 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_InstallDialog(object): class Ui_InstallDialog(object):
def setupUi(self, InstallDialog): def setupUi(self, InstallDialog):
InstallDialog.setObjectName("InstallDialog") InstallDialog.setObjectName("InstallDialog")
InstallDialog.resize(179, 204) InstallDialog.resize(197, 195)
InstallDialog.setWindowTitle("InstallDialog") InstallDialog.setWindowTitle("InstallDialog")
self.main_layout = QtWidgets.QFormLayout(InstallDialog) self.main_layout = QtWidgets.QFormLayout(InstallDialog)
self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.main_layout.setObjectName("main_layout") 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 = QtWidgets.QLabel(InstallDialog)
self.install_dir_label.setObjectName("install_dir_label") 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 = QtWidgets.QLabel(InstallDialog)
self.platform_label.setObjectName("platform_label") 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) self.platform_combo = QtWidgets.QComboBox(InstallDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
@ -35,43 +32,43 @@ class Ui_InstallDialog(object):
sizePolicy.setHeightForWidth(self.platform_combo.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.platform_combo.sizePolicy().hasHeightForWidth())
self.platform_combo.setSizePolicy(sizePolicy) self.platform_combo.setSizePolicy(sizePolicy)
self.platform_combo.setObjectName("platform_combo") 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 = QtWidgets.QLabel(InstallDialog)
self.shortcut_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.shortcut_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.shortcut_label.setObjectName("shortcut_label") 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 = QtWidgets.QCheckBox(InstallDialog)
self.shortcut_check.setText("") self.shortcut_check.setText("")
self.shortcut_check.setObjectName("shortcut_check") 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 = QtWidgets.QVBoxLayout()
self.selectable_layout.setObjectName("selectable_layout") 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 = QtWidgets.QVBoxLayout()
self.advanced_layout.setObjectName("advanced_layout") 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 = QtWidgets.QLabel(InstallDialog)
self.download_size_label.setObjectName("download_size_label") 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) self.download_size_text = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont() font = QtGui.QFont()
font.setItalic(True) font.setItalic(True)
self.download_size_text.setFont(font) self.download_size_text.setFont(font)
self.download_size_text.setObjectName("download_size_text") 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 = QtWidgets.QLabel(InstallDialog)
self.install_size_label.setObjectName("install_size_label") 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) self.install_size_text = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont() font = QtGui.QFont()
font.setItalic(True) font.setItalic(True)
self.install_size_text.setFont(font) self.install_size_text.setFont(font)
self.install_size_text.setWordWrap(True) self.install_size_text.setWordWrap(True)
self.install_size_text.setObjectName("install_size_text") 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 = QtWidgets.QLabel(InstallDialog)
self.avail_space_label.setObjectName("avail_space_label") 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) self.avail_space = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont() font = QtGui.QFont()
font.setBold(True) font.setBold(True)
@ -79,10 +76,10 @@ class Ui_InstallDialog(object):
self.avail_space.setFont(font) self.avail_space.setFont(font)
self.avail_space.setText("") self.avail_space.setText("")
self.avail_space.setObjectName("avail_space") 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 = QtWidgets.QLabel(InstallDialog)
self.warning_label.setObjectName("warning_label") 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) self.warning_text = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont() font = QtGui.QFont()
font.setItalic(True) font.setItalic(True)
@ -92,13 +89,12 @@ class Ui_InstallDialog(object):
self.warning_text.setWordWrap(True) self.warning_text.setWordWrap(True)
self.warning_text.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.warning_text.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse)
self.warning_text.setObjectName("warning_text") 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) self.retranslateUi(InstallDialog)
def retranslateUi(self, InstallDialog): def retranslateUi(self, InstallDialog):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
self.title_label.setText(_translate("InstallDialog", "error"))
self.install_dir_label.setText(_translate("InstallDialog", "Install folder")) self.install_dir_label.setText(_translate("InstallDialog", "Install folder"))
self.platform_label.setText(_translate("InstallDialog", "Platform")) self.platform_label.setText(_translate("InstallDialog", "Platform"))
self.shortcut_label.setText(_translate("InstallDialog", "Create shortcut")) self.shortcut_label.setText(_translate("InstallDialog", "Create shortcut"))

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>179</width> <width>197</width>
<height>204</height> <height>195</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -17,28 +17,21 @@
<property name="labelAlignment"> <property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<item row="0" column="0" colspan="2"> <item row="0" column="0">
<widget class="QLabel" name="title_label">
<property name="text">
<string>error</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="install_dir_label"> <widget class="QLabel" name="install_dir_label">
<property name="text"> <property name="text">
<string>Install folder</string> <string>Install folder</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="1" column="0">
<widget class="QLabel" name="platform_label"> <widget class="QLabel" name="platform_label">
<property name="text"> <property name="text">
<string>Platform</string> <string>Platform</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="platform_combo"> <widget class="QComboBox" name="platform_combo">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@ -48,7 +41,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="2" column="0">
<widget class="QLabel" name="shortcut_label"> <widget class="QLabel" name="shortcut_label">
<property name="text"> <property name="text">
<string>Create shortcut</string> <string>Create shortcut</string>
@ -58,27 +51,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="2" column="1">
<widget class="QCheckBox" name="shortcut_check"> <widget class="QCheckBox" name="shortcut_check">
<property name="text"> <property name="text">
<string notr="true"/> <string notr="true"/>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2"> <item row="3" column="0" colspan="2">
<layout class="QVBoxLayout" name="selectable_layout"/> <layout class="QVBoxLayout" name="selectable_layout"/>
</item> </item>
<item row="5" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<layout class="QVBoxLayout" name="advanced_layout"/> <layout class="QVBoxLayout" name="advanced_layout"/>
</item> </item>
<item row="6" column="0"> <item row="5" column="0">
<widget class="QLabel" name="download_size_label"> <widget class="QLabel" name="download_size_label">
<property name="text"> <property name="text">
<string>Download size</string> <string>Download size</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="5" column="1">
<widget class="QLabel" name="download_size_text"> <widget class="QLabel" name="download_size_text">
<property name="font"> <property name="font">
<font> <font>
@ -90,14 +83,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="6" column="0">
<widget class="QLabel" name="install_size_label"> <widget class="QLabel" name="install_size_label">
<property name="text"> <property name="text">
<string>Total install size</string> <string>Total install size</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="6" column="1">
<widget class="QLabel" name="install_size_text"> <widget class="QLabel" name="install_size_text">
<property name="font"> <property name="font">
<font> <font>
@ -112,14 +105,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="0"> <item row="7" column="0">
<widget class="QLabel" name="avail_space_label"> <widget class="QLabel" name="avail_space_label">
<property name="text"> <property name="text">
<string>Available space</string> <string>Available space</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="7" column="1">
<widget class="QLabel" name="avail_space"> <widget class="QLabel" name="avail_space">
<property name="font"> <property name="font">
<font> <font>
@ -132,14 +125,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item row="8" column="0">
<widget class="QLabel" name="warning_label"> <widget class="QLabel" name="warning_label">
<property name="text"> <property name="text">
<string>Warning</string> <string>Warning</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="8" column="1">
<widget class="QLabel" name="warning_text"> <widget class="QLabel" name="warning_text">
<property name="font"> <property name="font">
<font> <font>

View file

@ -14,102 +14,71 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_EosWidget(object): class Ui_EosWidget(object):
def setupUi(self, EosWidget): def setupUi(self, EosWidget):
EosWidget.setObjectName("EosWidget") EosWidget.setObjectName("EosWidget")
EosWidget.resize(586, 146) EosWidget.resize(464, 98)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(EosWidget.sizePolicy().hasHeightForWidth())
EosWidget.setSizePolicy(sizePolicy)
EosWidget.setWindowTitle("GroupBox") 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.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
self.eos_layout.setObjectName("eos_layout") self.eos_layout.setObjectName("eos_layout")
self.overlay_stack = QtWidgets.QStackedWidget(EosWidget) 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_stack.setObjectName("overlay_stack")
self.overlay_info_page = QtWidgets.QWidget() self.install_page = QtWidgets.QWidget()
self.overlay_info_page.setObjectName("overlay_info_page") self.install_page.setObjectName("install_page")
self.overlay_info_layout = QtWidgets.QFormLayout(self.overlay_info_page) self.install_page_layout = QtWidgets.QHBoxLayout(self.install_page)
self.overlay_info_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.install_page_layout.setContentsMargins(0, 0, 0, 0)
self.overlay_info_layout.setFormAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft) self.install_page_layout.setObjectName("install_page_layout")
self.overlay_info_layout.setObjectName("overlay_info_layout") self.install_label_layout = QtWidgets.QFormLayout()
self.installed_version_info_lbl = QtWidgets.QLabel(self.overlay_info_page) self.install_label_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.installed_version_info_lbl.setObjectName("installed_version_info_lbl") self.install_label_layout.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.overlay_info_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.installed_version_info_lbl) self.install_label_layout.setObjectName("install_label_layout")
self.installed_version_lbl = QtWidgets.QLabel(self.overlay_info_page) self.install_label = QtWidgets.QLabel(self.install_page)
self.installed_version_lbl.setText("error") self.install_label.setObjectName("install_label")
self.installed_version_lbl.setObjectName("installed_version_lbl") self.install_label_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.install_label)
self.overlay_info_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.installed_version_lbl) self.install_text = QtWidgets.QLabel(self.install_page)
self.installed_path_info_lbl = QtWidgets.QLabel(self.overlay_info_page) self.install_text.setObjectName("install_text")
self.installed_path_info_lbl.setObjectName("installed_path_info_lbl") self.install_label_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.install_text)
self.overlay_info_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.installed_path_info_lbl) self.install_page_layout.addLayout(self.install_label_layout)
self.installed_path_lbl = QtWidgets.QLabel(self.overlay_info_page) self.install_button_layout = QtWidgets.QVBoxLayout()
self.installed_path_lbl.setText("error") self.install_button_layout.setContentsMargins(-1, -1, 0, -1)
self.installed_path_lbl.setObjectName("installed_path_lbl") self.install_button_layout.setObjectName("install_button_layout")
self.overlay_info_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.installed_path_lbl) self.install_button = QtWidgets.QPushButton(self.install_page)
self.info_buttons_layout = QtWidgets.QHBoxLayout() self.install_button.setMinimumSize(QtCore.QSize(140, 0))
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_button.setObjectName("install_button") self.install_button.setObjectName("install_button")
self.install_buttons_layout.addWidget(self.install_button) self.install_button_layout.addWidget(self.install_button)
self.overlay_install_layout.setLayout(2, QtWidgets.QFormLayout.SpanningRole, self.install_buttons_layout) self.install_page_layout.addLayout(self.install_button_layout)
spacerItem3 = QtWidgets.QSpacerItem(6, 6, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.install_page_layout.setStretch(0, 1)
self.overlay_install_layout.setItem(1, QtWidgets.QFormLayout.SpanningRole, spacerItem3) self.overlay_stack.addWidget(self.install_page)
self.overlay_stack.addWidget(self.overlay_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.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.retranslateUi(EosWidget)
self.overlay_stack.setCurrentIndex(0) self.overlay_stack.setCurrentIndex(0)
@ -117,14 +86,13 @@ class Ui_EosWidget(object):
def retranslateUi(self, EosWidget): def retranslateUi(self, EosWidget):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
EosWidget.setTitle(_translate("EosWidget", "Epic Overlay")) EosWidget.setTitle(_translate("EosWidget", "Epic Overlay"))
self.installed_version_info_lbl.setText(_translate("EosWidget", "Version")) self.install_label.setText(_translate("EosWidget", "Status:"))
self.installed_path_info_lbl.setText(_translate("EosWidget", "Location")) self.install_text.setText(_translate("EosWidget", "Epic Online Services Overlay is not installed"))
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_button.setText(_translate("EosWidget", "Install")) 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__": if __name__ == "__main__":

View file

@ -6,179 +6,73 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>586</width> <width>464</width>
<height>146</height> <height>98</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string notr="true">GroupBox</string> <string notr="true">GroupBox</string>
</property> </property>
<property name="title"> <property name="title">
<string>Epic Overlay</string> <string>Epic Overlay</string>
</property> </property>
<layout class="QHBoxLayout" name="eos_layout" stretch="0,0"> <layout class="QVBoxLayout" name="eos_layout" stretch="0">
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum> <enum>QLayout::SetDefaultConstraint</enum>
</property> </property>
<item> <item>
<widget class="QStackedWidget" name="overlay_stack"> <widget class="QStackedWidget" name="overlay_stack">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="overlay_info_page"> <widget class="QWidget" name="install_page">
<layout class="QFormLayout" name="overlay_info_layout"> <layout class="QHBoxLayout" name="install_page_layout" stretch="1,0">
<property name="labelAlignment"> <property name="leftMargin">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <number>0</number>
</property> </property>
<property name="formAlignment"> <property name="topMargin">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> <number>0</number>
</property> </property>
<item row="0" column="0"> <property name="rightMargin">
<widget class="QLabel" name="installed_version_info_lbl"> <number>0</number>
<property name="text"> </property>
<string>Version</string> <property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QFormLayout" name="install_label_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
</widget> <property name="formAlignment">
</item> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
<item row="0" column="1">
<widget class="QLabel" name="installed_version_lbl">
<property name="text">
<string notr="true">error</string>
</property> </property>
</widget> <item row="0" column="0">
</item> <widget class="QLabel" name="install_label">
<item row="1" column="0">
<widget class="QLabel" name="installed_path_info_lbl">
<property name="text">
<string>Location</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="installed_path_lbl">
<property name="text">
<string notr="true">error</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="info_buttons_layout">
<item>
<spacer name="info_buttons_hspacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="uninstall_button">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>Uninstall</string> <string>Status:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="0" column="1">
<widget class="QPushButton" name="update_check_button"> <widget class="QLabel" name="install_text">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>Check for update</string> <string>Epic Online Services Overlay is not installed</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="update_button">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Update</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" colspan="2"> <item>
<spacer name="info_page_vspacer"> <layout class="QVBoxLayout" name="install_button_layout">
<property name="orientation"> <property name="rightMargin">
<enum>Qt::Vertical</enum> <number>0</number>
</property> </property>
<property name="sizeHint" stdset="0">
<size>
<width>6</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="overlay_install_page">
<layout class="QFormLayout" name="overlay_install_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Epic Overlay Services is not installed</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="install_buttons_layout">
<item>
<spacer name="install_buttons_hspacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QPushButton" name="install_button"> <widget class="QPushButton" name="install_button">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>120</width> <width>140</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -189,57 +83,86 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="0" colspan="2"> </layout>
<spacer name="install_page_vspacer"> </widget>
<property name="orientation"> <widget class="QWidget" name="info_page">
<enum>Qt::Vertical</enum> <layout class="QHBoxLayout" name="info_page_layout" stretch="1,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QFormLayout" name="info_label_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="formAlignment">
<size> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
<width>6</width>
<height>6</height>
</size>
</property> </property>
</spacer> <item row="0" column="0">
<widget class="QLabel" name="version_label">
<property name="text">
<string>Version:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="path_label">
<property name="text">
<string>Path:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="info_button_layout">
<item>
<widget class="QPushButton" name="update_button">
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Update</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uninstall_button">
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Uninstall</string>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item>
<widget class="QFrame" name="enable_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="enable_layout">
<item>
<widget class="QComboBox" name="select_pfx_combo"/>
</item>
<item>
<widget class="QCheckBox" name="enabled_cb">
<property name="text">
<string>Activated</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="enabled_info_label">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View file

@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_LegendarySettings(object): class Ui_LegendarySettings(object):
def setupUi(self, LegendarySettings): def setupUi(self, LegendarySettings):
LegendarySettings.setObjectName("LegendarySettings") LegendarySettings.setObjectName("LegendarySettings")
LegendarySettings.resize(681, 456) LegendarySettings.resize(608, 420)
LegendarySettings.setWindowTitle("LegendarySettings") LegendarySettings.setWindowTitle("LegendarySettings")
self.legendary_layout = QtWidgets.QHBoxLayout(LegendarySettings) self.legendary_layout = QtWidgets.QHBoxLayout(LegendarySettings)
self.legendary_layout.setObjectName("legendary_layout") self.legendary_layout.setObjectName("legendary_layout")
@ -128,26 +128,32 @@ class Ui_LegendarySettings(object):
self.right_layout.addWidget(self.cleanup_group) self.right_layout.addWidget(self.cleanup_group)
self.metadata_group = QtWidgets.QGroupBox(LegendarySettings) self.metadata_group = QtWidgets.QGroupBox(LegendarySettings)
self.metadata_group.setObjectName("metadata_group") self.metadata_group.setObjectName("metadata_group")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.metadata_group) self.metadata_layout = QtWidgets.QVBoxLayout(self.metadata_group)
self.verticalLayout_2.setObjectName("verticalLayout_2") self.metadata_layout.setObjectName("metadata_layout")
self.fetch_win32_check = QtWidgets.QCheckBox(self.metadata_group) self.fetch_win32_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_win32_check.setObjectName("fetch_win32_check") 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 = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_macos_check.setObjectName("fetch_macos_check") 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 = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_unreal_check.setObjectName("fetch_unreal_check") 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) self.metadata_info = QtWidgets.QLabel(self.metadata_group)
font = QtGui.QFont() font = QtGui.QFont()
font.setItalic(True) font.setItalic(True)
self.metadata_info.setFont(font) self.metadata_info.setFont(font)
self.metadata_info.setObjectName("metadata_info") 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 = QtWidgets.QPushButton(self.metadata_group)
self.refresh_metadata_button.setObjectName("refresh_metadata_button") 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) self.right_layout.addWidget(self.metadata_group)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.right_layout.addItem(spacerItem1) 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_win32_check.setText(_translate("LegendarySettings", "Include Win32 games"))
self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games")) self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games"))
self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine")) self.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.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply"))
self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata")) self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata"))

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>681</width> <width>608</width>
<height>456</height> <height>420</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -231,7 +231,7 @@
<property name="title"> <property name="title">
<string>Platforms</string> <string>Platforms</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="metadata_layout">
<item> <item>
<widget class="QCheckBox" name="fetch_win32_check"> <widget class="QCheckBox" name="fetch_win32_check">
<property name="text"> <property name="text">
@ -253,6 +253,30 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="exclude_non_asset_check">
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string>Exclude non-asset games</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="exclude_entitlements_check">
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string>Exclude entitlements</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="metadata_info"> <widget class="QLabel" name="metadata_info">
<property name="font"> <property name="font">

View file

@ -20,26 +20,39 @@ def save_config():
_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("%", "%%") value = value.replace("%%", "%").replace("%", "%%")
if not _config.has_section(app_name): if not _config.has_section(app_name):
_config[app_name] = {} _config[app_name] = {}
_config.set(app_name, option, value) _config.set(app_name, option, value)
save_config() 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): if _config.has_option(app_name, option):
_config.remove_option(app_name, option) _config.remove_option(app_name, option)
# if _config.has_section(app_name) and not _config[app_name]: # if _config.has_section(app_name) and not _config[app_name]:
# _config.remove_section(app_name) # _config.remove_section(app_name)
save_config() 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 return
# Disabled due to env variables implementation # Disabled due to env variables implementation
if _config.has_section(app_name): 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 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: def get_wine_prefix(app_name: Optional[str] = None, fallback: Any = None) -> str:
_prefix = os.path.join( _prefix = _config.get("default.env", "WINEPREFIX", fallback=fallback)
_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", "wine_prefix", fallback=_prefix) _prefix = _config.get("default", "wine_prefix", fallback=_prefix)
if app_name is not None: if app_name is not None:
_prefix = _config.get(f'{app_name}.env', 'WINEPREFIX', fallback=_prefix) _prefix = _config.get(f'{app_name}.env', 'WINEPREFIX', fallback=_prefix)
@ -79,10 +93,47 @@ def get_wine_prefixes() -> Set[str]:
_prefixes = [] _prefixes = []
for name, section in _config.items(): for name, section in _config.items():
pfx = section.get("WINEPREFIX") or section.get("wine_prefix") 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: if pfx:
_prefixes.append(pfx) _prefixes.append(pfx)
_prefixes = [os.path.expanduser(prefix) for prefix in _prefixes] _prefixes = [os.path.expanduser(prefix) for prefix in _prefixes]
return {p for p in _prefixes if os.path.isdir(p)} 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

View file

@ -1,9 +1,11 @@
import platform as pf
import os import os
from dataclasses import dataclass from dataclasses import dataclass
from logging import getLogger from logging import getLogger
from typing import Optional, Union, List, Dict from typing import Optional, Union, List, Dict
import vdf if pf.system() in {"Linux", "FreeBSD"}:
import vdf
logger = getLogger("Proton") logger = getLogger("Proton")

View file

@ -8,7 +8,6 @@ import orjson
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QNetworkDiskCache 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" 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]) 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.log = getLogger(f"{type(self).__name__}_{type(parent).__name__}")
self.manager = QNetworkAccessManager(self) self.manager = QNetworkAccessManager(self)
self.manager.finished.connect(self.__on_finished) self.manager.finished.connect(self.__on_finished)
self.manager.finished.connect(self.__process_next)
self.cache = None self.cache = None
if cache is not None: if cache is not None:
self.log.debug("Using cache dir %s", cache) self.log.debug("Using cache dir %s", cache)
@ -44,8 +42,7 @@ class QtRequests(QObject):
self.log.debug("Manager is authorized") self.log.debug("Manager is authorized")
self.token = token self.token = token
self.__pending_requests = [] self.__active_requests: Dict[QNetworkReply, RequestQueueItem] = {}
self.__active_requests = {}
@staticmethod @staticmethod
def __prepare_query(url, params) -> QUrl: def __prepare_query(url, params) -> QUrl:
@ -58,7 +55,7 @@ class QtRequests(QObject):
def __prepare_request(self, item: RequestQueueItem) -> QNetworkRequest: def __prepare_request(self, item: RequestQueueItem) -> QNetworkRequest:
request = QNetworkRequest(item.url) 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.setHeader(QNetworkRequest.UserAgentHeader, USER_AGENT)
request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, QNetworkRequest.NoLessSafeRedirectPolicy) request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, QNetworkRequest.NoLessSafeRedirectPolicy)
if self.cache is not None: if self.cache is not None:
@ -76,10 +73,7 @@ class QtRequests(QObject):
def post(self, url: str, handler: RequestHandler, payload: dict): def post(self, url: str, handler: RequestHandler, payload: dict):
item = RequestQueueItem(method="post", url=QUrl(url), payload=payload, handlers=[handler]) item = RequestQueueItem(method="post", url=QUrl(url), payload=payload, handlers=[handler])
if len(self.__active_requests) < REQUEST_LIMIT: self.__post(item)
self.__post(item)
else:
self.__pending_requests.append(item)
def __get(self, item: RequestQueueItem): def __get(self, item: RequestQueueItem):
request = self.__prepare_request(item) 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): 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) url = self.__prepare_query(url, params) if params is not None else QUrl(url)
item = RequestQueueItem(method="get", url=url, payload=payload, handlers=[handler]) item = RequestQueueItem(method="get", url=url, payload=payload, handlers=[handler])
if len(self.__active_requests) < REQUEST_LIMIT: self.__get(item)
self.__get(item)
else:
self.__pending_requests.append(item)
def __on_error(self, error: QNetworkReply.NetworkError) -> None: def __on_error(self, error: QNetworkReply.NetworkError) -> None:
self.log.error(error) self.log.error(error)
@ -102,19 +93,9 @@ class QtRequests(QObject):
def __parse_content_type(header) -> Tuple[str, str]: def __parse_content_type(header) -> Tuple[str, str]:
# lk: this looks weird but `cgi` is deprecated, PEP 594 suggests this way of parsing MIME # lk: this looks weird but `cgi` is deprecated, PEP 594 suggests this way of parsing MIME
m = Message() m = Message()
m['content-type'] = header m["content-type"] = header
return m.get_content_type(), m.get_content_charset() 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) @pyqtSlot(QNetworkReply)
def __on_finished(self, reply: QNetworkReply): def __on_finished(self, reply: QNetworkReply):
item = self.__active_requests.pop(reply, None) item = self.__active_requests.pop(reply, None)

View file

@ -101,9 +101,7 @@ class CollapsibleBase(object):
class CollapsibleFrame(QFrame, CollapsibleBase): class CollapsibleFrame(QFrame, CollapsibleBase):
def __init__( def __init__(self, animation_duration: int = 200, parent=None):
self, widget: QWidget = None, title: str = "", button_text: str = "", animation_duration: int = 200, parent=None
):
super(CollapsibleFrame, self).__init__(parent=parent) super(CollapsibleFrame, self).__init__(parent=parent)
self.setup(animation_duration) self.setup(animation_duration)
self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
@ -111,11 +109,10 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
self.toggle_button = QToolButton(self) self.toggle_button = QToolButton(self)
self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggle_button.setIcon(icon("fa.arrow-right")) self.toggle_button.setIcon(icon("fa.arrow-right"))
self.toggle_button.setText(button_text)
self.toggle_button.setCheckable(True) self.toggle_button.setCheckable(True)
self.toggle_button.setChecked(False) self.toggle_button.setChecked(False)
self.title_label = QLabel(title) self.title_label = QLabel(self)
font = self.title_label.font() font = self.title_label.font()
font.setBold(True) font.setBold(True)
self.title_label.setFont(font) self.title_label.setFont(font)
@ -134,8 +131,11 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
self.toggle_button.clicked.connect(self.animationStart) self.toggle_button.clicked.connect(self.animationStart)
if widget is not None: def setTitle(self, title: str):
self.setWidget(widget) self.title_label.setText(title)
def setText(self, text: str):
self.toggle_button.setText(text)
def isChecked(self) -> bool: def isChecked(self) -> bool:
return self.toggle_button.isChecked() return self.toggle_button.isChecked()
@ -156,12 +156,9 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
class CollapsibleGroupBox(QGroupBox, CollapsibleBase): class CollapsibleGroupBox(QGroupBox, CollapsibleBase):
def __init__( def __init__(self, animation_duration: int = 200, parent=None):
self, widget: QWidget = None, title: str = "", animation_duration: int = 200, parent=None
):
super(CollapsibleGroupBox, self).__init__(parent=parent) super(CollapsibleGroupBox, self).__init__(parent=parent)
self.setup(animation_duration) self.setup(animation_duration)
self.setTitle(title)
self.setCheckable(True) self.setCheckable(True)
self.setChecked(False) self.setChecked(False)
@ -173,9 +170,6 @@ class CollapsibleGroupBox(QGroupBox, CollapsibleBase):
self.toggled.connect(self.animationStart) self.toggled.connect(self.animationStart)
if widget is not None:
self.setWidget(widget)
def isChecked(self) -> bool: def isChecked(self) -> bool:
return super(CollapsibleGroupBox, self).isChecked() return super(CollapsibleGroupBox, self).isChecked()
@ -202,7 +196,9 @@ if __name__ == "__main__":
ui_frame = Ui_InstallDialogAdvanced() ui_frame = Ui_InstallDialogAdvanced()
widget_frame = QWidget() widget_frame = QWidget()
ui_frame.setupUi(widget_frame) 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) collapsible_frame.setDisabled(False)
def replace_func_frame(state): def replace_func_frame(state):
@ -217,7 +213,9 @@ if __name__ == "__main__":
ui_group = Ui_InstallDialogAdvanced() ui_group = Ui_InstallDialogAdvanced()
widget_group = QWidget() widget_group = QWidget()
ui_group.setupUi(widget_group) 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) collapsible_group.setDisabled(False)
def replace_func_group(state): def replace_func_group(state):

View file

@ -11,7 +11,7 @@ from PyQt5.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QHBoxLayout, QHBoxLayout,
QWidget, QWidget,
QLayout, QSpacerItem, QSizePolicy, QLayout, QSpacerItem, QSizePolicy, QLabel,
) )
from rare.utils.misc import icon from rare.utils.misc import icon
@ -74,6 +74,9 @@ class ButtonDialog(BaseDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super(ButtonDialog, self).__init__(parent=parent) super(ButtonDialog, self).__init__(parent=parent)
self.subtitle_label = QLabel(self)
self.subtitle_label.setVisible(False)
self.reject_button = QPushButton(self) self.reject_button = QPushButton(self)
self.reject_button.setText(self.tr("Cancel")) self.reject_button.setText(self.tr("Cancel"))
self.reject_button.setIcon(icon("fa.remove")) self.reject_button.setIcon(icon("fa.remove"))
@ -91,6 +94,7 @@ class ButtonDialog(BaseDialog):
self.button_layout.addWidget(self.accept_button) self.button_layout.addWidget(self.accept_button)
self.main_layout = QVBoxLayout(self) self.main_layout = QVBoxLayout(self)
self.main_layout.addWidget(self.subtitle_label)
# lk: dirty way to set a minimum width with fixed size constraint # lk: dirty way to set a minimum width with fixed size constraint
spacer = QSpacerItem( spacer = QSpacerItem(
480, self.main_layout.spacing(), 480, self.main_layout.spacing(),
@ -103,13 +107,23 @@ class ButtonDialog(BaseDialog):
def close(self): def close(self):
raise RuntimeError(f"Don't use `close()` with {type(self).__name__}") raise RuntimeError(f"Don't use `close()` with {type(self).__name__}")
def setSubtitle(self, text: str):
self.subtitle_label.setText(f"<b>{text}</b>")
self.subtitle_label.setVisible(True)
def setCentralWidget(self, widget: QWidget): def setCentralWidget(self, widget: QWidget):
widget.layout().setContentsMargins(0, 0, 0, 0) 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): def setCentralLayout(self, layout: QLayout):
layout.setContentsMargins(0, 0, 0, 0) 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 @abstractmethod
def accept_handler(self): def accept_handler(self):

View file

@ -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

View file

@ -1,4 +1,4 @@
requests requests<3.0
QtAwesome QtAwesome
setuptools setuptools
legendary-gl>=0.20.34 legendary-gl>=0.20.34

View file

@ -1,4 +1,4 @@
requests requests<3.0
PyQt5 PyQt5
QtAwesome QtAwesome
setuptools setuptools

View file

@ -1,9 +1,9 @@
requests requests<3.0
PyQt5 PyQt5
QtAwesome QtAwesome
setuptools setuptools
legendary-gl>=0.20.34; platform_system != "Windows" or platform_system != "Darwin" 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" legendary-gl @ git+https://github.com/derrod/legendary@96e07ff ; platform_system == "Windows" or platform_system == "Darwin"
orjson orjson
vdf; platform_system != "Windows" vdf; platform_system == "Linux" or platform_system == "FreeBSD"
pywin32; platform_system == "Windows" pywin32; platform_system == "Windows"