1
0
Fork 0
mirror of synced 2024-05-24 06:19:55 +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:
pylint:
runs-on: ubuntu-latest
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: [3.9, 3.10, 3.11, 3.12]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
python-version: ${{ matris.version }}
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip

View file

@ -12,7 +12,7 @@ from requests import HTTPError
from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow
from rare.shared import RareCore
from rare.utils import config_helper, paths
from rare.utils import paths
from rare.utils.misc import ExitCodes
from rare.widgets.rare_app import RareApp, RareAppException

View file

@ -6,8 +6,7 @@ from typing import Tuple, List, Union, Optional
from PyQt5.QtCore import QThreadPool, QSettings
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QVBoxLayout, QFormLayout
from legendary.utils.selective_dl import get_sdl_appname
from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QFormLayout
from rare.models.game import RareGame
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel
@ -15,18 +14,46 @@ from rare.shared.workers.install_info import InstallInfoWorker
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
from rare.utils.misc import format_size, icon
from rare.widgets.dialogs import ActionDialog, dialog_title_game
from rare.widgets.collapsible_widget import CollapsibleFrame
from rare.widgets.dialogs import ActionDialog, dialog_title_game
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
from rare.widgets.selective_widget import SelectiveWidget
class InstallDialogAdvanced(CollapsibleFrame):
def __init__(self, parent=None):
widget = QWidget(parent)
title = widget.tr("Advanced options")
super(InstallDialogAdvanced, self).__init__(parent=parent)
title = self.tr("Advanced options")
self.setTitle(title)
self.widget = QWidget(parent=self)
self.ui = Ui_InstallDialogAdvanced()
self.ui.setupUi(widget)
super(InstallDialogAdvanced, self).__init__(widget=widget, title=title, parent=parent)
self.ui.setupUi(self.widget)
self.setWidget(self.widget)
class InstallDialogSelective(CollapsibleFrame):
stateChanged: pyqtSignal = pyqtSignal()
def __init__(self, rgame: RareGame, parent=None):
super(InstallDialogSelective, self).__init__(parent=parent)
title = self.tr("Optional downloads")
self.setTitle(title)
self.setEnabled(bool(rgame.sdl_name))
self.widget: SelectiveWidget = None
self.rgame = rgame
def update_list(self, platform: str):
if self.widget is not None:
self.widget.deleteLater()
self.widget = SelectiveWidget(self.rgame, platform, parent=self)
self.widget.stateChanged.connect(self.stateChanged)
self.setWidget(self.widget)
def install_tags(self):
return self.widget.install_tags()
class InstallDialog(ActionDialog):
@ -48,13 +75,12 @@ class InstallDialog(ActionDialog):
header = self.tr("Modify")
bicon = icon("fa.gear")
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
self.setSubtitle(dialog_title_game(header, rgame.app_title))
install_widget = QWidget(self)
self.ui = Ui_InstallDialog()
self.ui.setupUi(install_widget)
self.ui.title_label.setText(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>")
self.core = rgame.core
self.rgame = rgame
self.__options: InstallOptionsModel = options
@ -64,7 +90,8 @@ class InstallDialog(ActionDialog):
self.advanced = InstallDialogAdvanced(parent=self)
self.ui.advanced_layout.addWidget(self.advanced)
self.selectable = CollapsibleFrame(widget=None, title=self.tr("Optional downloads"), parent=self)
self.selectable = InstallDialogSelective(rgame, parent=self)
self.selectable.stateChanged.connect(self.option_changed)
self.ui.selectable_layout.addWidget(self.selectable)
self.options_changed = False
@ -82,13 +109,14 @@ class InstallDialog(ActionDialog):
self.install_dir_edit = PathEdit(
path=base_path,
file_mode=QFileDialog.DirectoryOnly,
edit_func=self.option_changed,
save_func=self.save_install_edit,
edit_func=self.install_dir_edit_callback,
save_func=self.install_dir_save_callback,
parent=self,
)
self.ui.main_layout.setWidget(
self.ui.main_layout.getWidgetPosition(self.ui.install_dir_label)[0],
QFormLayout.FieldRole, self.install_dir_edit
QFormLayout.FieldRole,
self.install_dir_edit,
)
self.install_dir_edit.setDisabled(rgame.is_installed)
@ -102,10 +130,10 @@ class InstallDialog(ActionDialog):
self.ui.platform_combo.addItems(reversed(rgame.platforms))
combo_text = rgame.igame.platform if rgame.is_installed else rgame.default_platform
self.ui.platform_combo.setCurrentIndex(self.ui.platform_combo.findText(combo_text))
self.ui.platform_combo.currentIndexChanged.connect(lambda i: self.option_changed(None))
self.ui.platform_combo.currentIndexChanged.connect(self.option_changed)
self.ui.platform_combo.currentIndexChanged.connect(self.check_incompatible_platform)
self.ui.platform_combo.currentIndexChanged.connect(self.reset_install_dir)
self.ui.platform_combo.currentIndexChanged.connect(self.reset_sdl_list)
self.ui.platform_combo.currentTextChanged.connect(self.selectable.update_list)
self.ui.platform_label.setDisabled(rgame.is_installed)
self.ui.platform_combo.setDisabled(rgame.is_installed)
@ -131,11 +159,8 @@ class InstallDialog(ActionDialog):
lambda: self.non_reload_option_changed("shortcut")
)
self.selectable_checks: List[TagCheckBox] = []
self.config_tags: Optional[List[str]] = None
self.reset_install_dir(self.ui.platform_combo.currentIndex())
self.reset_sdl_list(self.ui.platform_combo.currentIndex())
self.selectable.update_list(self.ui.platform_combo.currentText())
self.check_incompatible_platform(self.ui.platform_combo.currentIndex())
self.accept_button.setEnabled(False)
@ -180,7 +205,7 @@ class InstallDialog(ActionDialog):
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
self.save_install_edit(self.install_dir_edit.text())
self.install_dir_save_callback(self.install_dir_edit.text())
super().showEvent(a0)
def execute(self):
@ -197,68 +222,31 @@ class InstallDialog(ActionDialog):
default_dir = self.core.get_default_install_dir(platform)
self.install_dir_edit.setText(default_dir)
@pyqtSlot(int)
def reset_sdl_list(self, index: int):
platform = self.ui.platform_combo.itemText(index)
for cb in self.selectable_checks:
cb.disconnect()
cb.deleteLater()
self.selectable_checks.clear()
if config_tags := self.core.lgd.config.get(self.rgame.app_name, 'install_tags', fallback=None):
self.config_tags = config_tags.split(",")
config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, 'disable_sdl', fallback=False)
sdl_name = get_sdl_appname(self.rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
if sdl_data:
widget = QWidget(self.selectable)
layout = QVBoxLayout(widget)
layout.setSpacing(0)
for tag, info in sdl_data.items():
cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
if tag == "__required":
cb.setChecked(True)
cb.setDisabled(True)
if self.config_tags is not None:
if all(elem in self.config_tags for elem in info["tags"]):
cb.setChecked(True)
layout.addWidget(cb)
self.selectable_checks.append(cb)
for cb in self.selectable_checks:
cb.stateChanged.connect(self.option_changed)
self.selectable.setWidget(widget)
else:
self.selectable.setDisabled(True)
@pyqtSlot(int)
def check_incompatible_platform(self, index: int):
platform = self.ui.platform_combo.itemText(index)
if platform == "Mac" and pf.system() != "Darwin":
self.error_box(
self.tr("Warning"),
self.tr("You will not be able to run the game if you select <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:
self.error_box()
def get_options(self):
self.__options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text()
base_path = os.path.join(self.install_dir_edit.text(), ".overlay" if self.__options.overlay else "")
self.__options.base_path = "" if self.rgame.is_installed else base_path
self.__options.platform = self.ui.platform_combo.currentText()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
self.__options.max_workers = self.advanced.ui.max_workers_spin.value()
self.__options.shared_memory = self.advanced.ui.max_memory_spin.value()
self.__options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
self.__options.force = self.advanced.ui.force_download_check.isChecked()
self.__options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
self.__options.no_install = self.advanced.ui.download_only_check.isChecked()
self.__options.platform = self.ui.platform_combo.currentText()
self.__options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
if self.selectable_checks:
self.__options.install_tag = [""]
for cb in self.selectable_checks:
if data := cb.isChecked():
# noinspection PyTypeChecker
self.__options.install_tag.extend(data)
self.__options.install_tag = self.selectable.install_tags()
self.__options.reset_sdl = True
def get_download_info(self):
self.__download = None
@ -279,13 +267,17 @@ class InstallDialog(ActionDialog):
self.get_options()
self.get_download_info()
def option_changed(self, path) -> Tuple[bool, str, int]:
@pyqtSlot()
def option_changed(self):
self.options_changed = True
self.accept_button.setEnabled(False)
self.action_button.setEnabled(not self.active())
def install_dir_edit_callback(self, path: str) -> Tuple[bool, str, int]:
self.option_changed()
return True, path, IndicatorReasonsCommon.VALID
def save_install_edit(self, path: str):
def install_dir_save_callback(self, path: str):
if not os.path.exists(path):
return
_, _, free_space = shutil.disk_usage(path)
@ -369,12 +361,6 @@ class InstallDialog(ActionDialog):
self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download)
def reject_handler(self):
# FIXME: This is implemented through the selective downloads dialog now. remove soon
# if self.config_tags is not None:
# config_helper.set_option(self.rgame.app_name, 'install_tags', ','.join(self.config_tags))
# else:
# # lk: this is purely for cleaning any install tags we might have added erroneously to the config
# config_helper.remove_option(self.rgame.app_name, 'install_tags')
self.__queue_item = InstallQueueItemModel(options=self.__options, download=None)
@ -387,6 +373,3 @@ class TagCheckBox(QCheckBox):
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False

View file

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

View file

@ -1,18 +1,11 @@
from typing import List, Union, Optional
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import (
QLabel,
QVBoxLayout,
QCheckBox,
QLayout, QGroupBox,
)
from legendary.utils.selective_dl import get_sdl_appname
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLayout, QGroupBox
from rare.models.game import RareGame
from rare.models.install import SelectiveDownloadsModel
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
from rare.utils.misc import icon
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
from rare.widgets.selective_widget import SelectiveWidget
class SelectiveDialog(ButtonDialog):
@ -22,23 +15,18 @@ class SelectiveDialog(ButtonDialog):
super(SelectiveDialog, self).__init__(parent=parent)
header = self.tr("Optional downloads for")
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
self.setSubtitle(dialog_title_game(header, rgame.app_title))
title_label = QLabel(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>", self)
self.core = rgame.core
self.rgame = rgame
self.selective_widget = SelectiveWidget(rgame, rgame.igame.platform, self)
selectable_group = QGroupBox(self.tr("Optional downloads"), self)
self.selectable_layout = QVBoxLayout(selectable_group)
self.selectable_layout.setSpacing(0)
self.selectable_checks: List[TagCheckBox] = []
self.config_tags: Optional[List[str]] = None
container = QGroupBox(self.tr("Optional downloads"), self)
container_layout = QVBoxLayout(container)
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.addWidget(self.selective_widget)
layout = QVBoxLayout()
layout.setSizeConstraint(QLayout.SetFixedSize)
layout.addWidget(title_label)
layout.addWidget(selectable_group)
layout.addWidget(container)
self.setCentralLayout(layout)
@ -47,62 +35,13 @@ class SelectiveDialog(ButtonDialog):
self.options: SelectiveDownloadsModel = SelectiveDownloadsModel(rgame.app_name)
config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, "disable_sdl", fallback=False)
sdl_name = get_sdl_appname(self.rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
self.create_sdl_list()
else:
self.options.accepted = True
self.accept()
def create_sdl_list(self):
platform = self.rgame.igame.platform
for cb in self.selectable_checks:
cb.disconnect()
cb.deleteLater()
self.selectable_checks.clear()
if config_tags := self.core.lgd.config.get(self.rgame.app_name, "install_tags", fallback=None):
self.config_tags = config_tags.split(",")
config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, "disable_sdl", fallback=False)
sdl_name = get_sdl_appname(self.rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
if sdl_data:
for tag, info in sdl_data.items():
cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
if tag == "__required":
cb.setChecked(True)
cb.setDisabled(True)
if self.config_tags is not None:
if all(elem in self.config_tags for elem in info["tags"]):
cb.setChecked(True)
self.selectable_layout.addWidget(cb)
self.selectable_checks.append(cb)
def done_handler(self):
self.result_ready.emit(self.rgame, self.options)
def accept_handler(self):
install_tag = [""]
for cb in self.selectable_checks:
if data := cb.isChecked():
# noinspection PyTypeChecker
install_tag.extend(data)
self.options.accepted = True
self.options.install_tag = install_tag
self.options.install_tag = self.selective_widget.install_tags()
def reject_handler(self):
self.options.accepted = False
self.options.install_tag = None
class TagCheckBox(QCheckBox):
def __init__(self, text, desc, tags: List[str], parent=None):
super(TagCheckBox, self).__init__(parent)
self.setText(text)
self.setToolTip(desc)
self.tags = tags
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False

View file

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

View file

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

View file

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

View file

@ -1,11 +1,11 @@
from typing import Optional
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLabel, QSpacerItem, QSizePolicy
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLabel, QSizePolicy
from rare.widgets.side_tab import SideTabWidget
from .egl_sync_group import EGLSyncGroup
from .eos_group import EOSGroup
from .eos_group import EosGroup
from .import_group import ImportGroup
from .ubisoft_group import UbisoftGroup
@ -34,8 +34,8 @@ class IntegrationsTabs(SideTabWidget):
self.tr(""),
self,
)
self.eos_group = EosGroup(self.eos_ubisoft)
self.ubisoft_group = UbisoftGroup(self.eos_ubisoft)
self.eos_group = EOSGroup(self.eos_ubisoft)
self.eos_ubisoft.addWidget(self.eos_group)
self.eos_ubisoft.addWidget(self.ubisoft_group)
self.eos_ubisoft_index = self.addTab(self.eos_ubisoft, self.tr("Epic Overlay and Ubisoft"))

View file

@ -1,259 +1,280 @@
import os
import platform
from logging import getLogger
from typing import List
from typing import Optional
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool
from PyQt5.QtWidgets import QGroupBox, QMessageBox
from legendary.lfs import eos
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, Qt, pyqtSlot, QSize
from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import (
QGroupBox,
QMessageBox,
QFrame,
QHBoxLayout,
QSizePolicy,
QLabel,
QPushButton,
QFormLayout,
QComboBox,
)
from rare.models.install import InstallOptionsModel
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
from rare.lgndr.core import LegendaryCore
from rare.models.game import RareEosOverlay
from rare.shared import RareCore
from rare.ui.components.tabs.games.integrations.eos_widget import Ui_EosWidget
from rare.utils import config_helper as config
from rare.utils.misc import icon
from rare.widgets.elide_label import ElideLabel
logger = getLogger("EpicOverlay")
def get_wine_prefixes() -> List[str]:
prefixes = list()
if os.path.exists(p := os.path.expanduser("~/.wine")):
prefixes.append(p)
for name, section in LegendaryCoreSingleton().lgd.config.items():
pfx = section.get("WINEPREFIX") or section.get("wine_prefix")
if pfx and pfx not in prefixes:
prefixes.append(pfx)
return prefixes
class CheckForUpdateWorker(QRunnable):
class CheckForUpdateSignals(QObject):
update_available = pyqtSignal(bool)
def __init__(self):
def __init__(self, core: LegendaryCore):
super(CheckForUpdateWorker, self).__init__()
self.signals = self.CheckForUpdateSignals()
self.setAutoDelete(True)
self.core = LegendaryCoreSingleton()
self.core = core
def run(self) -> None:
self.core.check_for_overlay_updates()
self.signals.update_available.emit(self.core.overlay_update_available)
class EOSGroup(QGroupBox):
class EosPrefixWidget(QFrame):
def __init__(self, overlay: RareEosOverlay, prefix: Optional[str], parent=None):
super(EosPrefixWidget, self).__init__(parent=parent)
self.setFrameShape(QFrame.StyledPanel)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.indicator = QLabel(parent=self)
self.indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
self.prefix_label = ElideLabel(
prefix.replace(os.path.expanduser("~"), "~") if prefix is not None else overlay.app_title,
parent=self,
)
self.overlay_label = ElideLabel(parent=self)
self.overlay_label.setDisabled(True)
self.path_select = QComboBox(self)
self.path_select.setMaximumWidth(150)
self.path_select.setMinimumWidth(150)
self.button = QPushButton(parent=self)
self.button.setMinimumWidth(150)
layout = QHBoxLayout(self)
layout.setContentsMargins(-1, 0, 0, 0)
layout.addWidget(self.indicator)
layout.addWidget(self.prefix_label, stretch=2)
layout.addWidget(self.overlay_label, stretch=3)
layout.addWidget(self.path_select)
layout.addWidget(self.button)
self.overlay = overlay
self.prefix = prefix
self.path_select.currentIndexChanged.connect(self.path_changed)
self.button.clicked.connect(self.action)
self.overlay.signals.game.installed.connect(self.update_state)
self.overlay.signals.game.uninstalled.connect(self.update_state)
self.update_state()
@pyqtSlot(int)
def path_changed(self, index: int) -> None:
path = self.path_select.itemData(index, Qt.UserRole)
active_path = os.path.normpath(p) if (p := self.overlay.active_path(self.prefix)) else ""
if self.overlay.is_enabled(self.prefix) and (path == active_path):
self.button.setText(self.tr("Disable overlay"))
else:
self.button.setText(self.tr("Enable overlay"))
@pyqtSlot()
def update_state(self) -> None:
active_path = os.path.normpath(p) if (p := self.overlay.active_path(self.prefix)) else ""
self.overlay_label.setText(f"<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):
super(EOSGroup, self).__init__(parent=parent)
super(EosGroup, self).__init__(parent=parent)
self.ui = Ui_EosWidget()
self.ui.setupUi(self)
# lk: set object names for CSS properties
self.ui.install_button.setObjectName("InstallButton")
self.ui.install_button.setIcon(icon("ri.install-line"))
self.ui.uninstall_button.setObjectName("UninstallButton")
self.ui.install_page_layout.setAlignment(Qt.AlignTop)
self.ui.info_page_layout.setAlignment(Qt.AlignTop)
self.ui.install_button.setIcon(icon("ri.install-line"))
self.ui.uninstall_button.setIcon(icon("ri.uninstall-line"))
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
self.installed_path_label = ElideLabel(parent=self)
self.installed_version_label = ElideLabel(parent=self)
self.prefix_enabled = False
self.ui.info_label_layout.setWidget(0, QFormLayout.FieldRole, self.installed_version_label)
self.ui.info_label_layout.setWidget(1, QFormLayout.FieldRole, self.installed_path_label)
self.ui.enabled_cb.stateChanged.connect(self.change_enable)
self.rcore = RareCore.instance()
self.core = self.rcore.core()
self.signals = self.rcore.signals()
self.overlay = self.rcore.get_overlay()
self.overlay.signals.widget.update.connect(self.update_state)
self.overlay.signals.game.installed.connect(self.install_finished)
self.overlay.signals.game.uninstalled.connect(self.uninstall_finished)
self.ui.install_button.clicked.connect(self.install_overlay)
self.ui.update_button.clicked.connect(self.install_overlay)
self.ui.uninstall_button.clicked.connect(self.uninstall_overlay)
self.ui.update_button.setVisible(False)
self.overlay = self.core.lgd.get_overlay_install_info()
self.signals.application.overlay_installed.connect(self.overlay_installation_finished)
self.signals.application.prefix_updated.connect(self.update_prefixes)
self.ui.update_check_button.clicked.connect(self.check_for_update)
self.ui.install_button.clicked.connect(self.install_overlay)
self.ui.update_button.clicked.connect(lambda: self.install_overlay(True))
if self.overlay: # installed
self.ui.installed_version_lbl.setText(f"<b>{self.overlay.version}</b>")
self.ui.installed_path_lbl.setText(f"<b>{self.overlay.install_path}</b>")
self.ui.overlay_stack.setCurrentIndex(0)
if self.overlay.is_installed: # installed
self.installed_version_label.setText(f"<b>{self.overlay.version}</b>")
self.installed_path_label.setText(os.path.normpath(self.overlay.install_path))
self.ui.overlay_stack.setCurrentWidget(self.ui.info_page)
else:
self.ui.overlay_stack.setCurrentIndex(1)
self.ui.enable_frame.setDisabled(True)
if platform.system() == "Windows":
self.current_prefix = None
self.ui.select_pfx_combo.setVisible(False)
else:
self.current_prefix = os.path.expanduser("~/.wine") \
if os.path.exists(os.path.expanduser("~/.wine")) \
else None
pfxs = get_wine_prefixes()
for pfx in pfxs:
self.ui.select_pfx_combo.addItem(pfx.replace(os.path.expanduser("~/"), "~/"))
if not pfxs:
self.ui.enable_frame.setDisabled(True)
else:
self.ui.select_pfx_combo.setCurrentIndex(0)
self.ui.select_pfx_combo.currentIndexChanged.connect(self.update_select_combo)
if pfxs:
self.update_select_combo(None)
self.ui.enabled_info_label.setText("")
self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
self.ui.update_button.setEnabled(False)
self.threadpool = QThreadPool.globalInstance()
self.worker: Optional[CheckForUpdateWorker] = None
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
self.check_for_update()
self.update_prefixes()
self.update_state()
super().showEvent(a0)
@pyqtSlot()
def update_state(self):
self.ui.install_button.setEnabled(self.overlay.state == RareEosOverlay.State.IDLE)
self.ui.update_button.setEnabled(self.overlay.state == RareEosOverlay.State.IDLE and self.overlay.has_update)
self.ui.uninstall_button.setEnabled(self.overlay.state == RareEosOverlay.State.IDLE)
def update_prefixes(self):
logger.debug("Updated prefixes")
pfxs = get_wine_prefixes() # returns /home/whatever
self.ui.select_pfx_combo.clear()
for widget in self.findChildren(EosPrefixWidget, options=Qt.FindDirectChildrenOnly):
widget.deleteLater()
for pfx in pfxs:
self.ui.select_pfx_combo.addItem(pfx.replace(os.path.expanduser("~/"), "~/"))
if platform.system() != "Windows":
prefixes = config.get_prefixes()
prefixes = {prefix for prefix in prefixes if config.prefix_exists(prefix)}
if platform.system() == "Darwin":
# TODO: add crossover support
pass
for prefix in prefixes:
widget = EosPrefixWidget(self.overlay, prefix)
self.ui.eos_layout.addWidget(widget)
logger.debug("Updated prefixes")
else:
widget = EosPrefixWidget(self.overlay, None)
self.ui.eos_layout.addWidget(widget)
if self.current_prefix in pfxs:
self.ui.select_pfx_combo.setCurrentIndex(
self.ui.select_pfx_combo.findText(self.current_prefix.replace(os.path.expanduser("~/"), "~/")))
@pyqtSlot(bool)
def check_for_update_finished(self, update_available: bool):
self.worker = None
self.ui.update_button.setEnabled(update_available)
def check_for_update(self):
def worker_finished(update_available):
self.ui.update_button.setVisible(update_available)
self.ui.update_check_button.setDisabled(False)
if not update_available:
self.ui.update_check_button.setText(self.tr("No update available"))
self.ui.update_check_button.setDisabled(True)
worker = CheckForUpdateWorker()
worker.signals.update_available.connect(worker_finished)
QThreadPool.globalInstance().start(worker)
def overlay_installation_finished(self):
self.overlay = self.core.lgd.get_overlay_install_info()
if not self.overlay:
logger.error("Something went wrong, when installing overlay")
QMessageBox.warning(self, "Error", self.tr("Something went wrong, when installing overlay"))
self.ui.update_button.setEnabled(False)
if not self.overlay.is_installed:
return
self.ui.overlay_stack.setCurrentIndex(0)
self.ui.installed_version_lbl.setText(f"<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")):
if self.worker is not None:
return
self.current_prefix = prefix
reg_paths = eos.query_registry_entries(self.current_prefix)
overlay_enabled = False
if reg_paths['overlay_path'] and self.core.is_overlay_install(reg_paths['overlay_path']):
overlay_enabled = True
self.ui.enabled_cb.setChecked(overlay_enabled)
self.worker = CheckForUpdateWorker(self.core)
self.worker.signals.update_available.connect(self.check_for_update_finished)
QThreadPool.globalInstance().start(self.worker)
def change_enable(self):
enabled = self.ui.enabled_cb.isChecked()
if not enabled:
try:
eos.remove_registry_entries(self.current_prefix)
except PermissionError:
logger.error("Can't disable eos overlay")
QMessageBox.warning(self, "Error", self.tr(
"Failed to disable Overlay. Probably it is installed by Epic Games Launcher"))
return
logger.info("Disabled Epic Overlay")
self.ui.enabled_info_label.setText(self.tr("Disabled"))
else:
if not self.overlay:
available_installs = self.core.search_overlay_installs(self.current_prefix)
if not available_installs:
logger.error('No EOS overlay installs found!')
return
path = available_installs[0]
else:
path = self.overlay.install_path
@pyqtSlot()
def install_finished(self):
if not self.overlay.is_installed:
logger.error("Something went wrong while installing overlay")
QMessageBox.warning(self, "Error", self.tr("Something went wrong while installing Overlay"))
return
self.ui.overlay_stack.setCurrentWidget(self.ui.info_page)
self.installed_version_label.setText(f"<b>{self.overlay.version}</b>")
self.installed_path_label.setText(self.overlay.install_path)
self.ui.update_button.setEnabled(False)
if not self.core.is_overlay_install(path):
logger.error(f'Not a valid Overlay installation: {path}')
self.ui.select_pfx_combo.removeItem(self.ui.select_pfx_combo.currentIndex())
return
@pyqtSlot()
def uninstall_finished(self):
self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
path = os.path.normpath(path)
reg_paths = eos.query_registry_entries(self.current_prefix)
if old_path := reg_paths["overlay_path"]:
if os.path.normpath(old_path) == path:
logger.info(f'Overlay already enabled, nothing to do.')
return
else:
logger.info(f'Updating overlay registry entries from "{old_path}" to "{path}"')
try:
eos.remove_registry_entries(self.current_prefix)
except PermissionError:
logger.error("Can't disable eos overlay")
QMessageBox.warning(self, "Error", self.tr(
"Failed to disable Overlay. Probably it is installed by Epic Games Launcher"))
return
try:
eos.add_registry_entries(path, self.current_prefix)
except PermissionError:
logger.error("Failed to disable eos overlay")
QMessageBox.warning(self, "Error", self.tr(
"Failed to enable EOS overlay. Maybe it is already installed by Epic Games Launcher"))
return
self.ui.enabled_info_label.setText(self.tr("Enabled"))
logger.info(f'Enabled overlay at: {path}')
def update_checkbox(self):
reg_paths = eos.query_registry_entries(self.current_prefix)
enabled = False
if reg_paths['overlay_path'] and self.core.is_overlay_install(reg_paths['overlay_path']):
enabled = True
self.ui.enabled_cb.setChecked(enabled)
def install_overlay(self, update=False):
base_path = os.path.join(self.core.get_default_install_dir(), ".overlay")
if update:
if not self.overlay:
self.ui.overlay_stack.setCurrentIndex(1)
self.ui.enable_frame.setDisabled(True)
QMessageBox.warning(self, "Warning", self.tr("Overlay is not installed. Could not update"))
return
base_path = self.overlay.install_path
options = InstallOptionsModel(
app_name=eos.EOSOverlayApp.app_name, base_path=base_path, platform="Windows", overlay=True
)
self.signals.game.install.emit(options)
@pyqtSlot()
def install_overlay(self):
self.overlay.install()
def uninstall_overlay(self):
if not self.core.is_overlay_installed():
logger.error('No legendary-managed overlay installation found.')
self.ui.overlay_stack.setCurrentIndex(1)
if not self.overlay.is_installed:
logger.error("No Legendary-managed overlay installation found.")
self.ui.overlay_stack.setCurrentWidget(self.ui.install_page)
return
if QMessageBox.No == QMessageBox.question(
self, "Uninstall Overlay", self.tr("Do you want to uninstall overlay?"),
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
):
return
if platform.system() == "Windows":
eos.remove_registry_entries(None)
else:
for prefix in [self.ui.select_pfx_combo.itemText(i) for i in range(self.ui.select_pfx_combo.count())]:
logger.info(f"Removing registry entries from {prefix}")
try:
eos.remove_registry_entries(os.path.expanduser(prefix))
except Exception as e:
logger.warning(f"{prefix}: {e}")
self.core.remove_overlay_install()
self.ui.overlay_stack.setCurrentIndex(1)
self.ui.enable_frame.setDisabled(True)
self.overlay.uninstall()

View file

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

View file

@ -116,6 +116,19 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked())
)
self.exclude_non_asset_check.setChecked(
self.settings.value("exclude_non_asset", False, bool)
)
self.exclude_non_asset_check.stateChanged.connect(
lambda: self.settings.setValue("exclude_non_asset", self.exclude_non_asset_check.isChecked())
)
self.exclude_entitlements_check.setChecked(
self.settings.value("exclude_entitlements", False, bool)
)
self.exclude_entitlements_check.stateChanged.connect(
lambda: self.settings.setValue("exclude_entitlements", self.exclude_entitlements_check.isChecked())
)
self.refresh_metadata_button.clicked.connect(self.refresh_metadata)
# FIXME: Disable the button for now because it interferes with RareCore
self.refresh_metadata_button.setEnabled(False)

View file

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

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']
rgame.igame = rgame.core.get_installed_game(app_name)
params: LaunchParameters = rgame.core.get_launch_parameters(
app_name=rgame.game.app_name, offline=args.offline, addon_app_name=rgame.igame.app_name
)
try:
params: LaunchParameters = rgame.core.get_launch_parameters(
app_name=rgame.game.app_name, offline=args.offline, addon_app_name=rgame.igame.app_name
)
except TypeError:
logger.warning("Using older get_launch_parameters due to legendary version")
params: LaunchParameters = rgame.core.get_launch_parameters(
app_name=rgame.game.app_name, offline=args.offline
)
full_params = []
launch_args.environment = QProcessEnvironment.systemEnvironment()

View file

@ -341,7 +341,7 @@ class LegendaryCLI(LegendaryCLIReal):
self.core.uninstall_tag(old_igame)
self.core.install_game(old_igame)
if old_igame.install_tags:
if old_igame and old_igame.install_tags:
self.core.lgd.config.set(game.app_name, 'install_tags', ','.join(old_igame.install_tags))
self.core.lgd.save_config()

View file

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

View file

@ -8,13 +8,13 @@ from threading import Lock
from typing import List, Optional, Dict, Set
from PyQt5.QtCore import QRunnable, pyqtSlot, QProcess, QThreadPool
from PyQt5.QtGui import QPixmap
from PyQt5.QtGui import QPixmap, QPixmapCache
from legendary.lfs import eos
from legendary.models.game import Game, InstalledGame
from legendary.utils.selective_dl import get_sdl_appname
from rare.lgndr.core import LegendaryCore
from rare.models.install import InstallOptionsModel, UninstallOptionsModel
from rare.models.base_game import RareGameBase, RareGameSlim
from rare.models.install import InstallOptionsModel, UninstallOptionsModel
from rare.shared.game_process import GameProcess
from rare.shared.image_manager import ImageManager
from rare.utils.paths import data_dir, get_rare_executable
@ -412,10 +412,6 @@ class RareGame(RareGameSlim):
else self.app_title
)
@property
def sdl_name(self) -> Optional[str]:
return get_sdl_appname(self.app_name)
@property
def save_path(self) -> Optional[str]:
return super(RareGame, self).save_path
@ -433,9 +429,12 @@ class RareGame(RareGameSlim):
elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date)
if self.metadata.steam_grade is not None and elapsed_time.days < 3:
return self.metadata.steam_grade
worker = QRunnable.create(
lambda: self.set_steam_grade(get_rating(self.core, self.app_name))
)
def _set_steam_grade():
rating = get_rating(self.core, self.app_name)
self.set_steam_grade(rating)
worker = QRunnable.create(_set_steam_grade)
QThreadPool.globalInstance().start(worker)
return "pending"
@ -446,9 +445,10 @@ class RareGame(RareGameSlim):
self.signals.widget.update.emit()
def grant_date(self, force=False) -> datetime:
if (entitlements := self.core.lgd.entitlements) is None:
return self.metadata.grant_date
if self.metadata.grant_date is None or force:
logger.debug("Grant date for %s not found in metadata, resolving", self.app_name)
entitlements = self.core.lgd.entitlements
matching = filter(lambda ent: ent["namespace"] == self.game.namespace, entitlements)
entitlement = next(matching, None)
grant_date = datetime.fromisoformat(
@ -481,6 +481,7 @@ class RareGame(RareGameSlim):
def set_pixmap(self):
self.pixmap = self.image_manager.get_pixmap(self.app_name, self.is_installed)
QPixmapCache.clear()
if not self.pixmap.isNull():
self.signals.widget.update.emit()
@ -572,3 +573,93 @@ class RareEosOverlay(RareGameBase):
else:
self.igame = None
self.signals.game.uninstalled.emit(self.app_name)
@property
def has_update(self) -> bool:
# lk: Don't check for updates here to ensure fast return
# There is already a thread in the EosGroup form to update it for us asynchronously
# and legendary does it too during login
return self.core.overlay_update_available
def is_enabled(self, prefix: Optional[str] = None) -> bool:
try:
reg_paths = eos.query_registry_entries(prefix)
except ValueError as e:
logger.info("%s %s", e, prefix)
return False
return reg_paths["overlay_path"] and self.core.is_overlay_install(reg_paths["overlay_path"])
def active_path(self, prefix: Optional[str] = None) -> str:
try:
path = eos.query_registry_entries(prefix)["overlay_path"]
except ValueError as e:
logger.info("%s %s", e, prefix)
return ""
return path if path and self.core.is_overlay_install(path) else ""
def available_paths(self, prefix: Optional[str] = None) -> List[str]:
try:
installs = self.core.search_overlay_installs(prefix)
except ValueError as e:
logger.info("%s %s", e, prefix)
return []
return installs
def enable(
self, prefix: Optional[str] = None, path: Optional[str] = None
) -> bool:
if self.is_enabled(prefix):
return False
if not path:
if self.is_installed:
path = self.igame.install_path
else:
path = self.available_paths(prefix)[-1]
reg_paths = eos.query_registry_entries(prefix)
if old_path := reg_paths["overlay_path"]:
if os.path.normpath(old_path) == path:
logger.info(f"Overlay already enabled, nothing to do.")
return True
else:
logger.info(f'Updating overlay registry entries from "{old_path}" to "{path}"')
eos.remove_registry_entries(prefix)
try:
eos.add_registry_entries(path, prefix)
except PermissionError as e:
logger.error("Exception while writing registry to enable the overlay.")
logger.error(e)
return False
logger.info(f"Enabled overlay at: {path} for prefix: {prefix}")
return True
def disable(self, prefix: Optional[str] = None) -> bool:
if not self.is_enabled(prefix):
return False
logger.info(f"Disabling overlay (removing registry keys) for prefix: {prefix}")
try:
eos.remove_registry_entries(prefix)
except PermissionError as e:
logger.error("Exception while writing registry to disable the overlay.")
logger.error(e)
return False
return True
def install(self) -> bool:
if not self.is_idle:
return False
self.signals.game.install.emit(
InstallOptionsModel(
app_name=self.app_name,
base_path=self.core.get_default_install_dir(),
platform="Windows", update=self.is_installed, overlay=True
)
)
return True
def uninstall(self) -> bool:
if not self.is_idle or not self.is_installed:
return False
self.signals.game.uninstall.emit(
UninstallOptionsModel(app_name=self.app_name)
)
return True

View file

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

View file

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

View file

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

View file

@ -64,7 +64,14 @@ class GameProcess(QObject):
self.tried_connections += 1
if self.tried_connections > 50: # 10 seconds
QMessageBox.warning(None, "Error", self.tr("Connection to game process failed (Timeout)"))
QMessageBox.warning(
None,
self.tr("Error - {}").format(self.game.app_title),
self.tr(
"Connection to game launcher for <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.finished.emit(GameProcess.Code.TIMEOUT)

View file

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

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,10 @@
import platform
from logging import getLogger
from typing import Tuple
from PyQt5.QtCore import QObject, pyqtSignal
from legendary.core import LegendaryCore
from legendary.lfs.eos import remove_registry_entries
from rare.lgndr.cli import LegendaryCLI
from rare.lgndr.glue.arguments import LgndrUninstallGameArgs
@ -16,14 +19,36 @@ logger = getLogger("UninstallWorker")
# TODO: You can use RareGame directly here once this is called inside RareCore and skip metadata fetch
def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_config=False):
game = core.get_game(app_name)
def uninstall_game(
core: LegendaryCore, rgame: RareGame, keep_files=False, keep_config=False, keep_overlay_keys=False
) -> Tuple[bool, str]:
if rgame.is_overlay:
logger.info('Deleting overlay installation...')
core.remove_overlay_install()
if keep_overlay_keys:
return True, ""
logger.info('Removing registry entries...')
if platform.system() != "Window":
prefixes = config_helper.get_wine_prefixes()
if platform.system() == "Darwin":
# TODO: add crossover support
pass
if prefixes is not None:
for prefix in prefixes:
remove_registry_entries(prefix)
logger.debug("Removed registry entries for prefix %s", prefix)
else:
remove_registry_entries()
return True, ""
# remove shortcuts link
if desktop_links_supported():
for link_type in desktop_link_types():
link_path = desktop_link_path(
game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value"), link_type
rgame.game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value"), link_type
)
if link_path.exists():
link_path.unlink(missing_ok=True)
@ -31,7 +56,7 @@ def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_co
status = LgndrIndirectStatus()
LegendaryCLI(core).uninstall_game(
LgndrUninstallGameArgs(
app_name=app_name,
app_name=rgame.app_name,
keep_files=keep_files,
skip_uninstaller=False,
yes=True,
@ -40,8 +65,8 @@ def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_co
)
if not keep_config:
logger.info("Removing sections in config file")
config_helper.remove_section(app_name)
config_helper.remove_section(f"{app_name}.env")
config_helper.remove_section(rgame.app_name)
config_helper.remove_section(f"{rgame.app_name}.env")
config_helper.save_config()
@ -62,7 +87,11 @@ class UninstallWorker(Worker):
def run_real(self) -> None:
self.rgame.state = RareGame.State.UNINSTALLING
success, message = uninstall_game(
self.core, self.rgame.app_name, self.options.keep_files, self.options.keep_config
self.core,
self.rgame,
self.options.keep_files,
self.options.keep_config,
self.options.keep_overlay_keys,
)
self.rgame.state = RareGame.State.IDLE
self.signals.result.emit(self.rgame, success, message)

View file

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

View file

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

View file

@ -14,102 +14,71 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_EosWidget(object):
def setupUi(self, EosWidget):
EosWidget.setObjectName("EosWidget")
EosWidget.resize(586, 146)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(EosWidget.sizePolicy().hasHeightForWidth())
EosWidget.setSizePolicy(sizePolicy)
EosWidget.resize(464, 98)
EosWidget.setWindowTitle("GroupBox")
self.eos_layout = QtWidgets.QHBoxLayout(EosWidget)
self.eos_layout = QtWidgets.QVBoxLayout(EosWidget)
self.eos_layout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
self.eos_layout.setObjectName("eos_layout")
self.overlay_stack = QtWidgets.QStackedWidget(EosWidget)
self.overlay_stack.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.overlay_stack.setFrameShadow(QtWidgets.QFrame.Raised)
self.overlay_stack.setObjectName("overlay_stack")
self.overlay_info_page = QtWidgets.QWidget()
self.overlay_info_page.setObjectName("overlay_info_page")
self.overlay_info_layout = QtWidgets.QFormLayout(self.overlay_info_page)
self.overlay_info_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.overlay_info_layout.setFormAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
self.overlay_info_layout.setObjectName("overlay_info_layout")
self.installed_version_info_lbl = QtWidgets.QLabel(self.overlay_info_page)
self.installed_version_info_lbl.setObjectName("installed_version_info_lbl")
self.overlay_info_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.installed_version_info_lbl)
self.installed_version_lbl = QtWidgets.QLabel(self.overlay_info_page)
self.installed_version_lbl.setText("error")
self.installed_version_lbl.setObjectName("installed_version_lbl")
self.overlay_info_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.installed_version_lbl)
self.installed_path_info_lbl = QtWidgets.QLabel(self.overlay_info_page)
self.installed_path_info_lbl.setObjectName("installed_path_info_lbl")
self.overlay_info_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.installed_path_info_lbl)
self.installed_path_lbl = QtWidgets.QLabel(self.overlay_info_page)
self.installed_path_lbl.setText("error")
self.installed_path_lbl.setObjectName("installed_path_lbl")
self.overlay_info_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.installed_path_lbl)
self.info_buttons_layout = QtWidgets.QHBoxLayout()
self.info_buttons_layout.setObjectName("info_buttons_layout")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.info_buttons_layout.addItem(spacerItem)
self.uninstall_button = QtWidgets.QPushButton(self.overlay_info_page)
self.uninstall_button.setMinimumSize(QtCore.QSize(120, 0))
self.uninstall_button.setObjectName("uninstall_button")
self.info_buttons_layout.addWidget(self.uninstall_button)
self.update_check_button = QtWidgets.QPushButton(self.overlay_info_page)
self.update_check_button.setMinimumSize(QtCore.QSize(120, 0))
self.update_check_button.setObjectName("update_check_button")
self.info_buttons_layout.addWidget(self.update_check_button)
self.update_button = QtWidgets.QPushButton(self.overlay_info_page)
self.update_button.setMinimumSize(QtCore.QSize(120, 0))
self.update_button.setObjectName("update_button")
self.info_buttons_layout.addWidget(self.update_button)
self.overlay_info_layout.setLayout(3, QtWidgets.QFormLayout.SpanningRole, self.info_buttons_layout)
spacerItem1 = QtWidgets.QSpacerItem(6, 6, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.overlay_info_layout.setItem(2, QtWidgets.QFormLayout.SpanningRole, spacerItem1)
self.overlay_stack.addWidget(self.overlay_info_page)
self.overlay_install_page = QtWidgets.QWidget()
self.overlay_install_page.setObjectName("overlay_install_page")
self.overlay_install_layout = QtWidgets.QFormLayout(self.overlay_install_page)
self.overlay_install_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.overlay_install_layout.setFormAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
self.overlay_install_layout.setObjectName("overlay_install_layout")
self.label = QtWidgets.QLabel(self.overlay_install_page)
self.label.setObjectName("label")
self.overlay_install_layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.label)
self.install_buttons_layout = QtWidgets.QHBoxLayout()
self.install_buttons_layout.setObjectName("install_buttons_layout")
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.install_buttons_layout.addItem(spacerItem2)
self.install_button = QtWidgets.QPushButton(self.overlay_install_page)
self.install_button.setMinimumSize(QtCore.QSize(120, 0))
self.install_page = QtWidgets.QWidget()
self.install_page.setObjectName("install_page")
self.install_page_layout = QtWidgets.QHBoxLayout(self.install_page)
self.install_page_layout.setContentsMargins(0, 0, 0, 0)
self.install_page_layout.setObjectName("install_page_layout")
self.install_label_layout = QtWidgets.QFormLayout()
self.install_label_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.install_label_layout.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.install_label_layout.setObjectName("install_label_layout")
self.install_label = QtWidgets.QLabel(self.install_page)
self.install_label.setObjectName("install_label")
self.install_label_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.install_label)
self.install_text = QtWidgets.QLabel(self.install_page)
self.install_text.setObjectName("install_text")
self.install_label_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.install_text)
self.install_page_layout.addLayout(self.install_label_layout)
self.install_button_layout = QtWidgets.QVBoxLayout()
self.install_button_layout.setContentsMargins(-1, -1, 0, -1)
self.install_button_layout.setObjectName("install_button_layout")
self.install_button = QtWidgets.QPushButton(self.install_page)
self.install_button.setMinimumSize(QtCore.QSize(140, 0))
self.install_button.setObjectName("install_button")
self.install_buttons_layout.addWidget(self.install_button)
self.overlay_install_layout.setLayout(2, QtWidgets.QFormLayout.SpanningRole, self.install_buttons_layout)
spacerItem3 = QtWidgets.QSpacerItem(6, 6, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.overlay_install_layout.setItem(1, QtWidgets.QFormLayout.SpanningRole, spacerItem3)
self.overlay_stack.addWidget(self.overlay_install_page)
self.install_button_layout.addWidget(self.install_button)
self.install_page_layout.addLayout(self.install_button_layout)
self.install_page_layout.setStretch(0, 1)
self.overlay_stack.addWidget(self.install_page)
self.info_page = QtWidgets.QWidget()
self.info_page.setObjectName("info_page")
self.info_page_layout = QtWidgets.QHBoxLayout(self.info_page)
self.info_page_layout.setContentsMargins(0, 0, 0, 0)
self.info_page_layout.setObjectName("info_page_layout")
self.info_label_layout = QtWidgets.QFormLayout()
self.info_label_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.info_label_layout.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.info_label_layout.setObjectName("info_label_layout")
self.version_label = QtWidgets.QLabel(self.info_page)
self.version_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.version_label.setObjectName("version_label")
self.info_label_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.version_label)
self.path_label = QtWidgets.QLabel(self.info_page)
self.path_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.path_label.setObjectName("path_label")
self.info_label_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.path_label)
self.info_page_layout.addLayout(self.info_label_layout)
self.info_button_layout = QtWidgets.QVBoxLayout()
self.info_button_layout.setObjectName("info_button_layout")
self.update_button = QtWidgets.QPushButton(self.info_page)
self.update_button.setMinimumSize(QtCore.QSize(140, 0))
self.update_button.setObjectName("update_button")
self.info_button_layout.addWidget(self.update_button)
self.uninstall_button = QtWidgets.QPushButton(self.info_page)
self.uninstall_button.setMinimumSize(QtCore.QSize(140, 0))
self.uninstall_button.setObjectName("uninstall_button")
self.info_button_layout.addWidget(self.uninstall_button)
self.info_page_layout.addLayout(self.info_button_layout)
self.info_page_layout.setStretch(0, 1)
self.overlay_stack.addWidget(self.info_page)
self.eos_layout.addWidget(self.overlay_stack)
self.enable_frame = QtWidgets.QFrame(EosWidget)
self.enable_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.enable_frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.enable_frame.setObjectName("enable_frame")
self.enable_layout = QtWidgets.QVBoxLayout(self.enable_frame)
self.enable_layout.setObjectName("enable_layout")
self.select_pfx_combo = QtWidgets.QComboBox(self.enable_frame)
self.select_pfx_combo.setObjectName("select_pfx_combo")
self.enable_layout.addWidget(self.select_pfx_combo)
self.enabled_cb = QtWidgets.QCheckBox(self.enable_frame)
self.enabled_cb.setObjectName("enabled_cb")
self.enable_layout.addWidget(self.enabled_cb)
self.enabled_info_label = QtWidgets.QLabel(self.enable_frame)
font = QtGui.QFont()
font.setItalic(True)
self.enabled_info_label.setFont(font)
self.enabled_info_label.setText("")
self.enabled_info_label.setObjectName("enabled_info_label")
self.enable_layout.addWidget(self.enabled_info_label)
self.eos_layout.addWidget(self.enable_frame)
self.retranslateUi(EosWidget)
self.overlay_stack.setCurrentIndex(0)
@ -117,14 +86,13 @@ class Ui_EosWidget(object):
def retranslateUi(self, EosWidget):
_translate = QtCore.QCoreApplication.translate
EosWidget.setTitle(_translate("EosWidget", "Epic Overlay"))
self.installed_version_info_lbl.setText(_translate("EosWidget", "Version"))
self.installed_path_info_lbl.setText(_translate("EosWidget", "Location"))
self.uninstall_button.setText(_translate("EosWidget", "Uninstall"))
self.update_check_button.setText(_translate("EosWidget", "Check for update"))
self.update_button.setText(_translate("EosWidget", "Update"))
self.label.setText(_translate("EosWidget", "Epic Overlay Services is not installed"))
self.install_label.setText(_translate("EosWidget", "Status:"))
self.install_text.setText(_translate("EosWidget", "Epic Online Services Overlay is not installed"))
self.install_button.setText(_translate("EosWidget", "Install"))
self.enabled_cb.setText(_translate("EosWidget", "Activated"))
self.version_label.setText(_translate("EosWidget", "Version:"))
self.path_label.setText(_translate("EosWidget", "Path:"))
self.update_button.setText(_translate("EosWidget", "Update"))
self.uninstall_button.setText(_translate("EosWidget", "Uninstall"))
if __name__ == "__main__":

View file

@ -6,179 +6,73 @@
<rect>
<x>0</x>
<y>0</y>
<width>586</width>
<height>146</height>
<width>464</width>
<height>98</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true">GroupBox</string>
</property>
<property name="title">
<string>Epic Overlay</string>
</property>
<layout class="QHBoxLayout" name="eos_layout" stretch="0,0">
<layout class="QVBoxLayout" name="eos_layout" stretch="0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<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">
<number>0</number>
</property>
<widget class="QWidget" name="overlay_info_page">
<layout class="QFormLayout" name="overlay_info_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<widget class="QWidget" name="install_page">
<layout class="QHBoxLayout" name="install_page_layout" stretch="1,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="formAlignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
<property name="topMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="installed_version_info_lbl">
<property name="text">
<string>Version</string>
<property name="rightMargin">
<number>0</number>
</property>
<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>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="installed_version_lbl">
<property name="text">
<string notr="true">error</string>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<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>
<item row="0" column="0">
<widget class="QLabel" name="install_label">
<property name="text">
<string>Uninstall</string>
<string>Status:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="update_check_button">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<item row="0" column="1">
<widget class="QLabel" name="install_text">
<property name="text">
<string>Check for update</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>
<string>Epic Online Services Overlay is not installed</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<spacer name="info_page_vspacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item>
<layout class="QVBoxLayout" name="install_button_layout">
<property name="rightMargin">
<number>0</number>
</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>
<widget class="QPushButton" name="install_button">
<property name="minimumSize">
<size>
<width>120</width>
<width>140</width>
<height>0</height>
</size>
</property>
@ -189,57 +83,86 @@
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<spacer name="install_page_vspacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</layout>
</widget>
<widget class="QWidget" name="info_page">
<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 name="sizeHint" stdset="0">
<size>
<width>6</width>
<height>6</height>
</size>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</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>
</layout>
</widget>
</widget>
</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>
</widget>
<resources/>

View file

@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_LegendarySettings(object):
def setupUi(self, LegendarySettings):
LegendarySettings.setObjectName("LegendarySettings")
LegendarySettings.resize(681, 456)
LegendarySettings.resize(608, 420)
LegendarySettings.setWindowTitle("LegendarySettings")
self.legendary_layout = QtWidgets.QHBoxLayout(LegendarySettings)
self.legendary_layout.setObjectName("legendary_layout")
@ -128,26 +128,32 @@ class Ui_LegendarySettings(object):
self.right_layout.addWidget(self.cleanup_group)
self.metadata_group = QtWidgets.QGroupBox(LegendarySettings)
self.metadata_group.setObjectName("metadata_group")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.metadata_group)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.metadata_layout = QtWidgets.QVBoxLayout(self.metadata_group)
self.metadata_layout.setObjectName("metadata_layout")
self.fetch_win32_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_win32_check.setObjectName("fetch_win32_check")
self.verticalLayout_2.addWidget(self.fetch_win32_check)
self.metadata_layout.addWidget(self.fetch_win32_check)
self.fetch_macos_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_macos_check.setObjectName("fetch_macos_check")
self.verticalLayout_2.addWidget(self.fetch_macos_check)
self.metadata_layout.addWidget(self.fetch_macos_check)
self.fetch_unreal_check = QtWidgets.QCheckBox(self.metadata_group)
self.fetch_unreal_check.setObjectName("fetch_unreal_check")
self.verticalLayout_2.addWidget(self.fetch_unreal_check)
self.metadata_layout.addWidget(self.fetch_unreal_check)
self.exclude_non_asset_check = QtWidgets.QCheckBox(self.metadata_group)
self.exclude_non_asset_check.setObjectName("exclude_non_asset_check")
self.metadata_layout.addWidget(self.exclude_non_asset_check)
self.exclude_entitlements_check = QtWidgets.QCheckBox(self.metadata_group)
self.exclude_entitlements_check.setObjectName("exclude_entitlements_check")
self.metadata_layout.addWidget(self.exclude_entitlements_check)
self.metadata_info = QtWidgets.QLabel(self.metadata_group)
font = QtGui.QFont()
font.setItalic(True)
self.metadata_info.setFont(font)
self.metadata_info.setObjectName("metadata_info")
self.verticalLayout_2.addWidget(self.metadata_info)
self.metadata_layout.addWidget(self.metadata_info)
self.refresh_metadata_button = QtWidgets.QPushButton(self.metadata_group)
self.refresh_metadata_button.setObjectName("refresh_metadata_button")
self.verticalLayout_2.addWidget(self.refresh_metadata_button)
self.metadata_layout.addWidget(self.refresh_metadata_button)
self.right_layout.addWidget(self.metadata_group)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.right_layout.addItem(spacerItem1)
@ -175,6 +181,14 @@ class Ui_LegendarySettings(object):
self.fetch_win32_check.setText(_translate("LegendarySettings", "Include Win32 games"))
self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games"))
self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine"))
self.exclude_non_asset_check.setToolTip(_translate("LegendarySettings", "Do not load metadata for non-asset games (i.e. Origin games) on start-up.\n"
"\n"
"Disabling this greatly improves start-up time, but some games might not be visible in your library."))
self.exclude_non_asset_check.setText(_translate("LegendarySettings", "Exclude non-asset games"))
self.exclude_entitlements_check.setToolTip(_translate("LegendarySettings", "Do not load entitlement data (i.e game\'s date of purchase) on start-up.\n"
"\n"
"Disabling this greatly improves start-up time, but some library filters may not work."))
self.exclude_entitlements_check.setText(_translate("LegendarySettings", "Exclude entitlements"))
self.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply"))
self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata"))

View file

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

View file

@ -20,26 +20,39 @@ def save_config():
_save_config()
def add_option(app_name: str, option: str, value: str):
def add_option(app_name: str, option: str, value: str) -> None:
value = value.replace("%%", "%").replace("%", "%%")
if not _config.has_section(app_name):
_config[app_name] = {}
_config.set(app_name, option, value)
save_config()
def remove_option(app_name, option):
def add_envvar(app_name: str, envvar: str, value: str) -> None:
add_option(f"{app_name}.env", envvar, value)
def remove_option(app_name: str, option: str) -> None:
if _config.has_option(app_name, option):
_config.remove_option(app_name, option)
# if _config.has_section(app_name) and not _config[app_name]:
# _config.remove_section(app_name)
save_config()
def remove_section(app_name):
def remove_envvar(app_name: str, option: str) -> None:
remove_option(f"{app_name}.env", option)
def get_option(app_name: str, option: str, fallback: Any = None) -> str:
return _config.get(app_name, option, fallback=fallback)
def get_envvar(app_name: str, option: str, fallback: Any = None) -> str:
return get_option(f"{app_name}.env", option, fallback=fallback)
def remove_section(app_name: str) -> None:
return
# Disabled due to env variables implementation
if _config.has_section(app_name):
@ -61,13 +74,14 @@ def get_game_envvar(option: str, app_name: Optional[str] = None, fallback: Any =
return _option
def get_proton_compat_data(app_name: Optional[str] = None, fallback: Any = None) -> str:
_compat = get_game_envvar("STEAM_COMPAT_DATA_PATH", app_name, fallback=fallback)
# return os.path.join(_compat, "pfx") if _compat else fallback
return _compat
def get_wine_prefix(app_name: Optional[str] = None, fallback: Any = None) -> str:
_prefix = os.path.join(
_config.get("default.env", "STEAM_COMPAT_DATA_PATH", fallback=fallback), "pfx")
if app_name is not None:
_prefix = os.path.join(
_config.get(f'{app_name}.env', "STEAM_COMPAT_DATA_PATH", fallback=_prefix), "pfx")
_prefix = _config.get("default.env", "WINEPREFIX", fallback=_prefix)
_prefix = _config.get("default.env", "WINEPREFIX", fallback=fallback)
_prefix = _config.get("default", "wine_prefix", fallback=_prefix)
if app_name is not None:
_prefix = _config.get(f'{app_name}.env', 'WINEPREFIX', fallback=_prefix)
@ -79,10 +93,47 @@ def get_wine_prefixes() -> Set[str]:
_prefixes = []
for name, section in _config.items():
pfx = section.get("WINEPREFIX") or section.get("wine_prefix")
if not pfx:
pfx = os.path.join(compatdata, "pfx") if (compatdata := section.get("STEAM_COMPAT_DATA_PATH")) else ""
if pfx:
_prefixes.append(pfx)
_prefixes = [os.path.expanduser(prefix) for prefix in _prefixes]
return {p for p in _prefixes if os.path.isdir(p)}
def get_proton_prefixes() -> Set[str]:
_prefixes = []
for name, section in _config.items():
pfx = os.path.join(compatdata, "pfx") if (compatdata := section.get("STEAM_COMPAT_DATA_PATH")) else ""
if pfx:
_prefixes.append(pfx)
_prefixes = [os.path.expanduser(prefix) for prefix in _prefixes]
return {p for p in _prefixes if os.path.isdir(p)}
def get_prefixes() -> Set[str]:
return get_wine_prefixes().union(get_proton_prefixes())
def prefix_exists(pfx: str) -> bool:
return os.path.isdir(pfx) and os.path.isfile(os.path.join(pfx, "user.reg"))
def get_prefix(app_name: str = "default") -> Optional[str]:
_compat_path = _config.get(f"{app_name}.env", "STEAM_COMPAT_DATA_PATH", fallback=None)
if _compat_path and prefix_exists(_compat_prefix := os.path.join(_compat_path, "pfx")):
return _compat_prefix
_wine_prefix = _config.get(f"{app_name}.env", "WINEPREFIX", fallback=None)
_wine_prefix = _config.get(app_name, "wine_prefix", fallback=_wine_prefix)
if _wine_prefix and prefix_exists(_wine_prefix):
return _wine_prefix
_compat_path = _config.get(f"default.env", "STEAM_COMPAT_DATA_PATH", fallback=None)
if _compat_path and prefix_exists(_compat_prefix := os.path.join(_compat_path, "pfx")):
return _compat_prefix
_wine_prefix = _config.get(f"default.env", "WINEPREFIX", fallback=None)
_wine_prefix = _config.get("default", "wine_prefix", fallback=_wine_prefix)
if _wine_prefix and prefix_exists(_wine_prefix):
return _wine_prefix
return None

View file

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

View file

@ -8,7 +8,6 @@ import orjson
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, QUrlQuery, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QNetworkDiskCache
REQUEST_LIMIT = 8
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"
RequestHandler = TypeVar("RequestHandler", bound=Callable[[Union[Dict, bytes]], None])
@ -33,7 +32,6 @@ class QtRequests(QObject):
self.log = getLogger(f"{type(self).__name__}_{type(parent).__name__}")
self.manager = QNetworkAccessManager(self)
self.manager.finished.connect(self.__on_finished)
self.manager.finished.connect(self.__process_next)
self.cache = None
if cache is not None:
self.log.debug("Using cache dir %s", cache)
@ -44,8 +42,7 @@ class QtRequests(QObject):
self.log.debug("Manager is authorized")
self.token = token
self.__pending_requests = []
self.__active_requests = {}
self.__active_requests: Dict[QNetworkReply, RequestQueueItem] = {}
@staticmethod
def __prepare_query(url, params) -> QUrl:
@ -58,7 +55,7 @@ class QtRequests(QObject):
def __prepare_request(self, item: RequestQueueItem) -> QNetworkRequest:
request = QNetworkRequest(item.url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json;charset=UTF-8")
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json; charset=UTF-8")
request.setHeader(QNetworkRequest.UserAgentHeader, USER_AGENT)
request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, QNetworkRequest.NoLessSafeRedirectPolicy)
if self.cache is not None:
@ -76,10 +73,7 @@ class QtRequests(QObject):
def post(self, url: str, handler: RequestHandler, payload: dict):
item = RequestQueueItem(method="post", url=QUrl(url), payload=payload, handlers=[handler])
if len(self.__active_requests) < REQUEST_LIMIT:
self.__post(item)
else:
self.__pending_requests.append(item)
self.__post(item)
def __get(self, item: RequestQueueItem):
request = self.__prepare_request(item)
@ -90,10 +84,7 @@ class QtRequests(QObject):
def get(self, url: str, handler: RequestHandler, payload: Dict = None, params: Dict = None):
url = self.__prepare_query(url, params) if params is not None else QUrl(url)
item = RequestQueueItem(method="get", url=url, payload=payload, handlers=[handler])
if len(self.__active_requests) < REQUEST_LIMIT:
self.__get(item)
else:
self.__pending_requests.append(item)
self.__get(item)
def __on_error(self, error: QNetworkReply.NetworkError) -> None:
self.log.error(error)
@ -102,19 +93,9 @@ class QtRequests(QObject):
def __parse_content_type(header) -> Tuple[str, str]:
# lk: this looks weird but `cgi` is deprecated, PEP 594 suggests this way of parsing MIME
m = Message()
m['content-type'] = header
m["content-type"] = header
return m.get_content_type(), m.get_content_charset()
def __process_next(self):
if self.__pending_requests:
item = self.__pending_requests.pop(0)
if item.method == "post":
self.__post(item)
elif item.method == "get":
self.__get(item)
else:
raise NotImplementedError
@pyqtSlot(QNetworkReply)
def __on_finished(self, reply: QNetworkReply):
item = self.__active_requests.pop(reply, None)

View file

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

View file

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

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
setuptools
legendary-gl>=0.20.34

View file

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

View file

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