1
0
Fork 0
mirror of synced 2024-05-18 19:42:54 +12:00

Merge pull request #364 from loathingKernel/next

Enable launchable addons such as Fortnite Experiences as games in the library.
This commit is contained in:
Stelios Tsampas 2024-01-02 19:04:48 +02:00 committed by GitHub
commit 4e0f3ab59c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 761 additions and 747 deletions

View file

@ -31,7 +31,7 @@ class RareException(RareAppException):
except Exception as e:
self.logger.fatal(str(e))
QMessageBox.warning(None, "Error", self.tr("Failed to login"))
QApplication.exit(1)
QApplication.quit()
return False
@ -51,7 +51,7 @@ class Rare(RareApp):
# set Application name for settings
self.main_window: Optional[MainWindow] = None
self.launch_dialog: Optional[LaunchDialog] = None
self.timer: Optional[QTimer] = None
self.relogin_timer: Optional[QTimer] = None
# This launches the application after it has been instantiated.
# The timer's signal will be serviced once we call `exec()` on the application
@ -61,15 +61,15 @@ class Rare(RareApp):
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]).replace(tzinfo=timezone.utc)
dt_now = datetime.utcnow().replace(tzinfo=timezone.utc)
td = abs(dt_exp - dt_now)
self.timer.start(int(td.total_seconds() - 60) * 1000)
self.relogin_timer.start(int(td.total_seconds() - 60) * 1000)
self.logger.info(f"Renewed session expires at {self.core.lgd.userdata['expires_at']}")
def re_login(self):
def relogin(self):
self.logger.info("Session expires shortly. Renew session")
try:
self.core.login()
self.core.login(force_refresh=True)
except requests.exceptions.ConnectionError:
self.timer.start(3000) # try again if no connection
self.relogin_timer.start(3000) # try again if no connection
return
self.poke_timer()
@ -85,8 +85,9 @@ class Rare(RareApp):
@pyqtSlot()
def __on_start_app(self):
self.timer = QTimer()
self.timer.timeout.connect(self.re_login)
self.relogin_timer = QTimer(self)
self.relogin_timer.setTimerType(Qt.VeryCoarseTimer)
self.relogin_timer.timeout.connect(self.relogin)
self.poke_timer()
self.main_window = MainWindow()
@ -105,10 +106,10 @@ class Rare(RareApp):
def __on_exit_app(self, exit_code=0):
threadpool = QThreadPool.globalInstance()
threadpool.waitForDone()
if self.timer is not None:
self.timer.stop()
self.timer.deleteLater()
self.timer = None
if self.relogin_timer is not None:
self.relogin_timer.stop()
self.relogin_timer.deleteLater()
self.relogin_timer = None
self.rcore.deleteLater()
del self.rcore
self.processEvents()

View file

@ -1,94 +0,0 @@
import datetime
import sys
from logging import getLogger
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QSizePolicy, QLayout, QApplication, QWidget
from legendary.core import LegendaryCore
from legendary.models.game import InstalledGame
from rare.ui.components.dialogs.sync_save_dialog import Ui_SyncSaveDialog
from rare.ui.components.tabs.games.game_info.sync_widget import Ui_SyncWidget
from rare.utils.misc import icon
logger = getLogger("Cloud Saves")
class CloudSaveDialog(QDialog, Ui_SyncSaveDialog):
DOWNLOAD = 2
UPLOAD = 1
CANCEL = 0
SKIP = 3
def __init__(
self,
igame: InstalledGame,
dt_local: datetime.datetime,
dt_remote: datetime.datetime,
):
super(CloudSaveDialog, self).__init__()
self.setupUi(self)
self.sync_widget = QWidget()
self.sync_ui = Ui_SyncWidget()
self.sync_ui.setupUi(self.sync_widget)
self.sync_widget_layout.addWidget(self.sync_widget)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.status = self.CANCEL
self.title_label.setText(f"{self.title_label.text()} <b>{igame.title}</b>")
newer = self.tr("Newer")
if dt_remote and dt_local:
self.sync_ui.age_label_local.setText(
f"<b>{newer}</b>" if dt_remote < dt_local else " "
)
self.sync_ui.age_label_remote.setText(
f"<b>{newer}</b>" if dt_remote > dt_local else " "
)
# Set status, if one of them is None
elif dt_remote and not dt_local:
self.status = self.DOWNLOAD
elif not dt_remote and dt_local:
self.status = self.UPLOAD
else:
self.status = self.SKIP
self.sync_ui.date_info_local.setText(dt_local.strftime("%A, %d. %B %Y %X") if dt_local else "None")
self.sync_ui.date_info_remote.setText(dt_remote.strftime("%A, %d. %B %Y %X") if dt_remote else "None")
self.sync_ui.icon_local.setPixmap(icon("mdi.harddisk", "fa.desktop").pixmap(128, 128))
self.sync_ui.icon_remote.setPixmap(icon("mdi.cloud-outline", "ei.cloud").pixmap(128, 128))
self.sync_ui.upload_button.clicked.connect(lambda: self.btn_clicked(self.UPLOAD))
self.sync_ui.download_button.clicked.connect(lambda: self.btn_clicked(self.DOWNLOAD))
self.cancel_button.clicked.connect(self.close)
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.layout().setSizeConstraint(QLayout.SetFixedSize)
def get_action(self):
if self.status == self.SKIP:
return self.SKIP
self.exec_()
return self.status
def btn_clicked(self, status):
self.status = status
self.close()
def test_dialog():
app = QApplication(sys.argv)
core = LegendaryCore()
dlg = CloudSaveDialog(core.get_installed_list()[0], datetime.datetime.now(),
datetime.datetime.strptime("2021,1", "%Y,%M"))
print(dlg.get_action())
if __name__ == '__main__':
test_dialog()

View file

@ -3,49 +3,63 @@ import platform as pf
import shutil
from typing import Tuple, List, Union, Optional
from PyQt5.QtCore import Qt, QThreadPool, QSettings, QCoreApplication
from PyQt5.QtCore import QThreadPool, QSettings
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QKeyEvent, QShowEvent
from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox, QLayout, QWidget, QVBoxLayout, QFormLayout
from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QVBoxLayout, QFormLayout
from legendary.utils.selective_dl import get_sdl_appname
from rare.models.game import RareGame
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton
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 import config_helper
from rare.utils.misc import format_size
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.indicator_edit import PathEdit, IndicatorReasonsCommon
class InstallDialogAdvanced(CollapsibleFrame):
def __init__(self, parent=None):
widget = QWidget()
widget = QWidget(parent)
title = widget.tr("Advanced options")
self.ui = Ui_InstallDialogAdvanced()
self.ui.setupUi(widget)
super(InstallDialogAdvanced, self).__init__(widget=widget, title=title, parent=parent)
class InstallDialog(QDialog):
class InstallDialog(ActionDialog):
result_ready = pyqtSignal(InstallQueueItemModel)
def __init__(self, rgame: RareGame, options: InstallOptionsModel, parent=None):
super(InstallDialog, self).__init__(parent=parent)
self.ui = Ui_InstallDialog()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
# lk: set object names for CSS properties
self.ui.install_button.setObjectName("InstallButton")
self.core = LegendaryCoreSingleton()
header = self.tr("Install")
bicon = icon("ri.install-line")
if options.repair_mode:
header = self.tr("Repair")
bicon = icon("fa.wrench")
if options.repair_and_update:
header = self.tr("Repair and update")
elif options.update:
header = self.tr("Update")
elif options.reset_sdl:
header = self.tr("Modify")
bicon = icon("fa.gear")
self.setWindowTitle(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 = options
self.__options: InstallOptionsModel = options
self.__download: Optional[InstallDownloadModel] = None
self.__queue_item: Optional[InstallQueueItemModel] = None
self.advanced = InstallDialogAdvanced(parent=self)
self.ui.advanced_layout.addWidget(self.advanced)
@ -54,25 +68,10 @@ class InstallDialog(QDialog):
self.ui.selectable_layout.addWidget(self.selectable)
self.options_changed = False
self.worker_running = False
self.reject_close = True
self.threadpool = QThreadPool(self)
self.threadpool.setMaxThreadCount(1)
if options.repair_mode:
header = self.tr("Repair")
if options.repair_and_update:
header = self.tr("Repair and update")
elif options.update:
header = self.tr("Update")
elif options.reset_sdl:
header = self.tr("Modify")
else:
header = self.tr("Install")
self.ui.install_dialog_label.setText(f'<h3>{header} "{self.rgame.app_title}"</h3>')
self.setWindowTitle(f'{header} "{self.rgame.app_title}" - {QCoreApplication.instance().applicationName()}')
if options.base_path:
base_path = options.base_path
elif rgame.is_installed:
@ -87,8 +86,8 @@ class InstallDialog(QDialog):
save_func=self.save_install_edit,
parent=self,
)
self.ui.install_dialog_layout.setWidget(
self.ui.install_dialog_layout.getWidgetPosition(self.ui.install_dir_label)[0],
self.ui.main_layout.setWidget(
self.ui.main_layout.getWidgetPosition(self.ui.install_dir_label)[0],
QFormLayout.FieldRole, self.install_dir_edit
)
@ -111,6 +110,11 @@ class InstallDialog(QDialog):
self.ui.platform_label.setDisabled(rgame.is_installed)
self.ui.platform_combo.setDisabled(rgame.is_installed)
# if we are repairing, disable the SDL selection and open the dialog frame to be visible
self.selectable.setDisabled(options.repair_mode and not options.repair_and_update)
if options.repair_mode and not options.repair_and_update:
self.selectable.click()
self.advanced.ui.max_workers_spin.setValue(self.core.lgd.config.getint("Legendary", "max_workers", fallback=0))
self.advanced.ui.max_workers_spin.valueChanged.connect(self.option_changed)
@ -134,9 +138,9 @@ class InstallDialog(QDialog):
self.reset_sdl_list(self.ui.platform_combo.currentIndex())
self.check_incompatible_platform(self.ui.platform_combo.currentIndex())
self.ui.install_button.setEnabled(False)
self.accept_button.setEnabled(False)
if self.options.overlay:
if self.__options.overlay:
self.ui.platform_label.setEnabled(False)
self.ui.platform_combo.setEnabled(False)
self.advanced.ui.ignore_space_label.setEnabled(False)
@ -161,13 +165,17 @@ class InstallDialog(QDialog):
self.non_reload_option_changed("shortcut")
self.ui.cancel_button.clicked.connect(self.__on_cancel)
self.ui.verify_button.clicked.connect(self.__on_verify)
self.ui.install_button.clicked.connect(self.__on_install)
self.advanced.ui.install_prereqs_check.setChecked(self.__options.install_prereqs)
self.advanced.ui.install_prereqs_check.setChecked(self.options.install_prereqs)
# lk: set object names for CSS properties
self.accept_button.setText(header)
self.accept_button.setIcon(bicon)
self.accept_button.setObjectName("InstallButton")
self.ui.install_dialog_layout.setSizeConstraint(QLayout.SetFixedSize)
self.action_button.setText(self.tr("Verify"))
self.action_button.setIcon(icon("fa.check"))
self.setCentralWidget(install_widget)
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
@ -176,13 +184,11 @@ class InstallDialog(QDialog):
super().showEvent(a0)
def execute(self):
if self.options.silent:
self.reject_close = False
if self.__options.silent:
self.get_download_info()
else:
self.setModal(True)
self.__on_verify()
self.show()
self.action_handler()
self.open()
@pyqtSlot(int)
def reset_install_dir(self, index: int):
@ -210,7 +216,7 @@ class InstallDialog(QDialog):
layout = QVBoxLayout(widget)
layout.setSpacing(0)
for tag, info in sdl_data.items():
cb = TagCheckBox(info["name"], info["description"], info["tags"])
cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
if tag == "__required":
cb.setChecked(True)
cb.setDisabled(True)
@ -237,50 +243,46 @@ class InstallDialog(QDialog):
self.error_box()
def get_options(self):
self.options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text()
self.options.max_workers = self.advanced.ui.max_workers_spin.value()
self.options.shared_memory = self.advanced.ui.max_memory_spin.value()
self.options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
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()
self.__options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text()
self.__options.max_workers = self.advanced.ui.max_workers_spin.value()
self.__options.shared_memory = self.advanced.ui.max_memory_spin.value()
self.__options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
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 = [""]
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.extend(data)
def get_download_info(self):
self.__download = None
info_worker = InstallInfoWorker(self.core, self.options)
info_worker = InstallInfoWorker(self.core, self.__options)
info_worker.signals.result.connect(self.on_worker_result)
info_worker.signals.failed.connect(self.on_worker_failed)
info_worker.signals.finished.connect(self.on_worker_finished)
self.worker_running = True
self.threadpool.start(info_worker)
def __on_verify(self):
def action_handler(self):
self.error_box()
message = self.tr("Updating...")
self.ui.download_size_text.setText(message)
self.ui.download_size_text.setStyleSheet("font-style: italic; font-weight: normal")
self.ui.install_size_text.setText(message)
self.ui.install_size_text.setStyleSheet("font-style: italic; font-weight: normal")
self.ui.cancel_button.setEnabled(False)
self.ui.verify_button.setEnabled(False)
self.ui.install_button.setEnabled(False)
self.setActive(True)
self.options_changed = False
self.get_options()
self.get_download_info()
def option_changed(self, path) -> Tuple[bool, str, int]:
self.options_changed = True
self.ui.install_button.setEnabled(False)
self.ui.verify_button.setEnabled(not self.worker_running)
self.accept_button.setEnabled(False)
self.action_button.setEnabled(not self.active())
return True, path, IndicatorReasonsCommon.VALID
def save_install_edit(self, path: str):
@ -291,33 +293,18 @@ class InstallDialog(QDialog):
def non_reload_option_changed(self, option: str):
if option == "download_only":
self.options.no_install = self.advanced.ui.download_only_check.isChecked()
self.__options.no_install = self.advanced.ui.download_only_check.isChecked()
elif option == "shortcut":
QSettings().setValue("create_shortcut", self.ui.shortcut_check.isChecked())
self.options.create_shortcut = self.ui.shortcut_check.isChecked()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
elif option == "install_prereqs":
self.options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
def __on_cancel(self):
if self.config_tags is not None:
config_helper.add_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.__download = None
self.reject_close = False
self.close()
def __on_install(self):
self.reject_close = False
self.close()
self.__options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
@staticmethod
def same_platform(download: InstallDownloadModel) -> bool:
platform = download.igame.platform
if pf.system() == "Windows":
return platform == "Windows" or platform == "Win32"
return platform in {"Windows", "Win32"}
elif pf.system() == "Darwin":
return platform == "Mac"
else:
@ -325,6 +312,7 @@ class InstallDialog(QDialog):
@pyqtSlot(InstallDownloadModel)
def on_worker_result(self, download: InstallDownloadModel):
self.setActive(False)
self.__download = download
download_size = download.analysis.dl_size
install_size = download.analysis.install_size
@ -332,14 +320,13 @@ class InstallDialog(QDialog):
if download_size or (not download_size and (download.game.is_dlc or download.repair)):
self.ui.download_size_text.setText(format_size(download_size))
self.ui.download_size_text.setStyleSheet("font-style: normal; font-weight: bold")
self.ui.install_button.setEnabled(not self.options_changed)
self.accept_button.setEnabled(not self.options_changed)
else:
self.ui.install_size_text.setText(self.tr("Game already installed"))
self.ui.install_size_text.setStyleSheet("font-style: italics; font-weight: normal")
self.ui.install_size_text.setText(format_size(install_size))
self.ui.install_size_text.setStyleSheet("font-style: normal; font-weight: bold")
self.ui.verify_button.setEnabled(self.options_changed)
self.ui.cancel_button.setEnabled(True)
self.action_button.setEnabled(self.options_changed)
has_prereqs = bool(download.igame.prereq_info) and not download.igame.prereq_info.get("installed", False)
if has_prereqs:
prereq_name = download.igame.prereq_info.get("name", "")
@ -352,18 +339,19 @@ class InstallDialog(QDialog):
self.advanced.ui.install_prereqs_label.setEnabled(has_prereqs)
self.advanced.ui.install_prereqs_check.setEnabled(has_prereqs)
self.advanced.ui.install_prereqs_check.setChecked(has_prereqs and self.same_platform(download))
if self.options.silent:
self.close()
if self.__options.silent:
self.accept()
def on_worker_failed(self, message: str):
self.setActive(False)
error_text = self.tr("Error")
self.ui.download_size_text.setText(error_text)
self.ui.install_size_text.setText(error_text)
self.error_box(error_text, message)
self.ui.verify_button.setEnabled(self.options_changed)
self.ui.cancel_button.setEnabled(True)
if self.options.silent:
self.show()
self.action_button.setEnabled(self.options_changed)
self.accept_button.setEnabled(False)
if self.__options.silent:
self.open()
def error_box(self, label: str = "", message: str = ""):
self.ui.warning_label.setVisible(bool(label))
@ -371,25 +359,23 @@ class InstallDialog(QDialog):
self.ui.warning_text.setVisible(bool(message))
self.ui.warning_text.setText(message)
def on_worker_finished(self):
self.worker_running = False
def done_handler(self):
self.threadpool.clear()
self.threadpool.waitForDone()
self.result_ready.emit(self.__queue_item)
# lk: happens when close() is called, also when top right 'X' is pressed.
# lk: reject any events not coming from the buttons in case the WM
# lk: doesn't honor the window hints
def closeEvent(self, a0: QCloseEvent) -> None:
if self.reject_close:
a0.ignore()
else:
self.threadpool.clear()
self.threadpool.waitForDone()
self.result_ready.emit(InstallQueueItemModel(options=self.options, download=self.__download))
super(InstallDialog, self).closeEvent(a0)
# lk: __download is already set at this point so just do nothing.
def accept_handler(self):
self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download)
def keyPressEvent(self, e: QKeyEvent) -> None:
if e.key() == Qt.Key_Escape:
e.accept()
self.__on_cancel()
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)
class TagCheckBox(QCheckBox):

View file

@ -49,17 +49,14 @@ class LaunchDialog(BaseDialog):
def login(self):
can_launch = True
try:
if self.args.offline:
pass
else:
if not self.args.offline:
# Force an update check and notice in case there are API changes
# self.core.check_for_updates(force=True)
# self.core.force_show_update = True
if self.core.login(force_refresh=True):
logger.info("You are logged in")
self.login_dialog.close()
else:
if not self.core.login(force_refresh=True):
raise ValueError("You are not logged in. Opening login window.")
logger.info("You are logged in")
self.login_dialog.close()
except ValueError as e:
logger.info(str(e))
# Do not set parent, because it won't show a task bar icon

View file

@ -143,11 +143,10 @@ class LoginDialog(BaseDialog):
def login_successful(self):
try:
if self.core.login():
self.logged_in = True
self.accept()
else:
if not self.core.login():
raise ValueError("Login failed.")
self.logged_in = True
self.accept()
except Exception as e:
logger.error(str(e))
self.core.lgd.invalidate_userdata()

View file

@ -92,9 +92,7 @@ class ImportLogin(QFrame):
def do_login(self):
self.ui.status_label.setText(self.tr("Loading..."))
if os.name == "nt":
pass
else:
if os.name != "nt":
logger.info("Using EGL appdata path at %s", {self.egl_appdata})
self.core.egl.appdata_path = self.egl_appdata
try:

View file

@ -0,0 +1,108 @@
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 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
class SelectiveDialog(ButtonDialog):
result_ready = pyqtSignal(RareGame, SelectiveDownloadsModel)
def __init__(self, rgame: RareGame, parent=None):
super(SelectiveDialog, self).__init__(parent=parent)
header = self.tr("Optional downloads for")
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
title_label = QLabel(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>", self)
self.core = rgame.core
self.rgame = rgame
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
layout = QVBoxLayout()
layout.setSizeConstraint(QLayout.SetFixedSize)
layout.addWidget(title_label)
layout.addWidget(selectable_group)
self.setCentralLayout(layout)
self.accept_button.setText(self.tr("Verify"))
self.accept_button.setIcon(icon("fa.check"))
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
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

@ -6,7 +6,7 @@ from PyQt5.QtWidgets import (
QVBoxLayout,
QCheckBox,
QHBoxLayout,
QPushButton,
QPushButton, QLayout,
)
from legendary.utils.selective_dl import get_sdl_appname
@ -24,8 +24,8 @@ class UninstallDialog(QDialog):
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
header = self.tr("Uninstall")
self.setWindowTitle(f'{header} "{rgame.app_title}" - {QCoreApplication.instance().applicationName()}')
self.info_text = QLabel(
self.tr("Do you really want to uninstall <b>{}</b>?").format(rgame.app_title)
self.title_label = QLabel(
self.tr("<h4>Do you really want to uninstall <b>{}</b>?</h4>").format(rgame.app_title)
)
self.keep_files = QCheckBox(self.tr("Keep game files."))
@ -52,14 +52,13 @@ class UninstallDialog(QDialog):
button_layout.addStretch(1)
button_layout.addWidget(self.uninstall_button)
layout = QVBoxLayout()
layout.addWidget(self.info_text)
layout = QVBoxLayout(self)
layout.setSizeConstraint(QLayout.SetFixedSize)
layout.addWidget(self.title_label)
layout.addLayout(form_layout)
layout.addLayout(button_layout)
self.setLayout(layout)
if get_sdl_appname(rgame.app_name) is not None:
if rgame.sdl_name is not None:
self.keep_config.setChecked(True)
self.options: UninstallOptionsModel = options

View file

@ -106,10 +106,10 @@ class MainWindow(QMainWindow):
except ModuleNotFoundError:
logger.warning("Discord RPC module not found")
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.timer_finished)
self.timer.start()
self.singleton_timer = QTimer(self)
self.singleton_timer.setInterval(1000)
self.singleton_timer.timeout.connect(self.timer_finished)
self.singleton_timer.start()
self.tray_icon: TrayIcon = TrayIcon(self)
self.tray_icon.exit_app.connect(self.__on_exit_app)
@ -193,13 +193,12 @@ class MainWindow(QMainWindow):
def timer_finished(self):
file_path = lock_file()
if os.path.exists(file_path):
file = open(file_path, "r")
action = file.read()
file.close()
with open(file_path, "r") as file:
action = file.read()
if action.startswith("show"):
self.show()
os.remove(file_path)
self.timer.start()
self.singleton_timer.start()
@pyqtSlot()
@pyqtSlot(int)
@ -258,7 +257,7 @@ class MainWindow(QMainWindow):
e.ignore()
return
# FIXME: End of FIXME
self.timer.stop()
self.singleton_timer.stop()
self.tray_icon.deleteLater()
self.hide()
self.exit_app.emit(self.__exit_code)

View file

@ -146,7 +146,7 @@ class GamesTab(QStackedWidget):
def update_count_games_label(self):
self.head_bar.set_games_count(
len([game for game in self.rcore.games if game.is_installed]),
len([game for game in self.rcore.games])
len(list(self.rcore.games)),
)
def setup_game_list(self):

View file

@ -13,15 +13,15 @@ from PyQt5.QtWidgets import (
QMessageBox,
QGroupBox,
QVBoxLayout,
QSpacerItem,
QSpacerItem, QFormLayout,
)
from legendary.models.game import SaveGameStatus
from rare.models.game import RareGame
from rare.shared import RareCore
from rare.shared.workers.wine_resolver import WineResolver
from rare.ui.components.tabs.games.game_info.cloud_widget import Ui_CloudWidget
from rare.ui.components.tabs.games.game_info.sync_widget import Ui_SyncWidget
from rare.ui.components.tabs.games.game_info.cloud_settings_widget import Ui_CloudSettingsWidget
from rare.ui.components.tabs.games.game_info.cloud_sync_widget import Ui_CloudSyncWidget
from rare.utils.misc import icon
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
from rare.widgets.loading_widget import LoadingWidget
@ -35,7 +35,7 @@ class CloudSaves(QWidget, SideTabContents):
super(CloudSaves, self).__init__(parent=parent)
self.sync_widget = QWidget(self)
self.sync_ui = Ui_SyncWidget()
self.sync_ui = Ui_CloudSyncWidget()
self.sync_ui.setupUi(self.sync_widget)
self.info_label = QLabel(self.tr("<b>This game doesn't support cloud saves</b>"))
@ -56,7 +56,7 @@ class CloudSaves(QWidget, SideTabContents):
self.rgame: RareGame = None
self.cloud_widget = QGroupBox(self)
self.cloud_ui = Ui_CloudWidget()
self.cloud_ui = Ui_CloudSettingsWidget()
self.cloud_ui.setupUi(self.cloud_widget)
self.cloud_save_path_edit = PathEdit(
@ -66,16 +66,20 @@ class CloudSaves(QWidget, SideTabContents):
edit_func=self.edit_save_path,
save_func=self.save_save_path,
)
self.cloud_ui.cloud_layout.addRow(QLabel(self.tr("Save path")), self.cloud_save_path_edit)
self.cloud_ui.main_layout.setWidget(
self.cloud_ui.main_layout.getWidgetPosition(self.cloud_ui.path_label)[0],
QFormLayout.FieldRole,
self.cloud_save_path_edit
)
self.compute_save_path_button = QPushButton(icon("fa.magic"), self.tr("Calculate path"))
self.compute_save_path_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
self.compute_save_path_button.clicked.connect(self.compute_save_path)
self.cloud_ui.cloud_layout.addRow(None, self.compute_save_path_button)
self.cloud_ui.main_layout.addRow(None, self.compute_save_path_button)
self.cloud_ui.cloud_sync.stateChanged.connect(
self.cloud_ui.sync_check.stateChanged.connect(
lambda: self.settings.setValue(
f"{self.rgame.app_name}/auto_sync_cloud", self.cloud_ui.cloud_sync.isChecked()
f"{self.rgame.app_name}/auto_sync_cloud", self.cloud_ui.sync_check.isChecked()
)
)
@ -139,7 +143,7 @@ class CloudSaves(QWidget, SideTabContents):
self.compute_save_path_button.setDisabled(False)
if path and not os.path.exists(path):
try:
os.makedirs(path)
os.makedirs(path, exist_ok=True)
except PermissionError:
self.cloud_save_path_edit.setText("")
QMessageBox.warning(
@ -172,7 +176,7 @@ class CloudSaves(QWidget, SideTabContents):
self.sync_ui.age_label_local.setText("None")
self.sync_ui.date_info_remote.setText("None")
self.sync_ui.age_label_remote.setText("None")
self.cloud_ui.cloud_sync.setChecked(False)
self.cloud_ui.sync_check.setChecked(False)
self.cloud_save_path_edit.setText("")
return
@ -203,9 +207,9 @@ class CloudSaves(QWidget, SideTabContents):
self.sync_ui.upload_button.setDisabled(not dt_local)
self.sync_ui.download_button.setDisabled(not dt_remote)
self.cloud_ui.cloud_sync.blockSignals(True)
self.cloud_ui.cloud_sync.setChecked(self.rgame.auto_sync_saves)
self.cloud_ui.cloud_sync.blockSignals(False)
self.cloud_ui.sync_check.blockSignals(True)
self.cloud_ui.sync_check.setChecked(self.rgame.auto_sync_saves)
self.cloud_ui.sync_check.blockSignals(False)
self.cloud_save_path_edit.setText(self.rgame.save_path if self.rgame.save_path else "")
if platform.system() == "Windows" and not self.rgame.save_path:

View file

@ -1,6 +1,7 @@
from typing import Optional, List
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import QFrame, QMessageBox, QToolBox
from rare.models.game import RareGame
@ -37,6 +38,14 @@ class GameDlcWidget(QFrame):
@pyqtSlot()
def __update(self):
self.ui.action_button.setEnabled(self.rdlc.is_idle)
self.image.setPixmap(self.rdlc.pixmap)
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
if self.rdlc.pixmap.isNull():
self.rdlc.load_pixmap()
super().showEvent(a0)
class InstalledGameDlcWidget(GameDlcWidget):

View file

@ -16,6 +16,8 @@ from PyQt5.QtWidgets import (
QWidgetAction,
)
from rare.models.install import SelectiveDownloadsModel
from rare.components.dialogs.selective_dialog import SelectiveDialog
from rare.models.game import RareGame
from rare.shared import RareCore
from rare.shared.workers import VerifyWorker, MoveWorker
@ -42,12 +44,12 @@ class GameInfo(QWidget, SideTabContents):
self.ui.uninstall_button.setObjectName("UninstallButton")
self.ui.install_button.setIcon(icon("ri.install-line"))
self.ui.import_button.setIcon(icon("mdi.file-import"))
self.ui.import_button.setIcon(icon("mdi.application-import"))
self.ui.modify_button.setIcon(icon("fa.gear"))
self.ui.verify_button.setIcon(icon("fa.check"))
self.ui.repair_button.setIcon(icon("fa.wrench"))
self.ui.move_button.setIcon(icon("mdi.folder-move"))
self.ui.move_button.setIcon(icon("mdi.folder-move-outline"))
self.ui.uninstall_button.setIcon(icon("ri.uninstall-line"))
self.rcore = RareCore.instance()
@ -162,9 +164,22 @@ class GameInfo(QWidget, SideTabContents):
self.tr("Installation path for <b>{}</b> does not exist. Cannot continue.").format(self.rgame.app_title),
)
return
self.verify_game(self.rgame)
if self.rgame.sdl_name is not None:
selective_dialog = SelectiveDialog(
self.rgame, parent=self
)
selective_dialog.result_ready.connect(self.verify_game)
selective_dialog.open()
else:
self.verify_game(self.rgame)
def verify_game(self, rgame: RareGame):
@pyqtSlot(RareGame, SelectiveDownloadsModel)
def verify_game(self, rgame: RareGame, sdl_model: SelectiveDownloadsModel = None):
if sdl_model is not None:
if not sdl_model.accepted or sdl_model.install_tag is None:
return
self.core.lgd.config.set(rgame.app_name, "install_tags", ','.join(sdl_model.install_tag))
self.core.lgd.save_config()
worker = VerifyWorker(self.core, self.args, rgame)
worker.signals.progress.connect(self.__on_verify_progress)
worker.signals.result.connect(self.__on_verify_result)
@ -378,7 +393,7 @@ class GameInfo(QWidget, SideTabContents):
self.ui.install_button.setText(self.tr("Link/Launch"))
self.ui.game_actions_stack.setCurrentWidget(self.ui.uninstalled_page)
else:
self.ui.install_button.setText(self.tr("Install Game"))
self.ui.install_button.setText(self.tr("Install"))
self.move_game_pop_up.update_game(rgame)

View file

@ -45,7 +45,7 @@ class EnvVars(QGroupBox):
layout.addWidget(self.table_view)
def keyPressEvent(self, e):
if e.key() == Qt.Key_Delete or e.key() == Qt.Key_Backspace:
if e.key() in {Qt.Key_Delete, Qt.Key_Backspace}:
indexes = self.table_view.selectedIndexes()
if not len(indexes):
return

View file

@ -97,7 +97,7 @@ class EnvVarsTableModel(QAbstractTableModel):
return value == match.group(0)
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
if role == Qt.DisplayRole or role == Qt.EditRole:
if role in {Qt.DisplayRole, Qt.EditRole}:
if index.row() == self.__data_length():
return ""
if index.column() == 0:

View file

@ -14,18 +14,22 @@ from PyQt5.QtNetwork import QLocalServer, QLocalSocket
from PyQt5.QtWidgets import QApplication
from legendary.models.game import SaveGameStatus
from rare.components.dialogs.cloud_save_dialog import CloudSaveDialog
from rare.lgndr.core import LegendaryCore
from rare.models.base_game import RareGameSlim
from rare.models.launcher import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
from rare.widgets.rare_app import RareApp, RareAppException
from .console import Console
from .cloud_sync_dialog import CloudSyncDialog, CloudSyncDialogResult
from .console_dialog import ConsoleDialog
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
DETACHED_APP_NAMES = [
"0a2d9f6403244d12969e11da6713137b" # Fall Guys
"Fortnite"
]
DETACHED_APP_NAMES = {
"0a2d9f6403244d12969e11da6713137b", # Fall Guys
"Fortnite",
"afdb5a85efcc45d8ae8e406e2121d81c", # Fortnite Battle Royale
"09e442f830a341f698b4da42abd98c9b", # Fortnite Festival
"d8f7763e07d74c209d760a679f9ed6ac", # Lego Fortnite
"Fortnite_Studio", # Unreal Editor for Fortnite
}
class PreLaunchThread(QRunnable):
@ -37,30 +41,29 @@ class PreLaunchThread(QRunnable):
def __init__(self, core: LegendaryCore, args: InitArgs, rgame: RareGameSlim, sync_action=None):
super(PreLaunchThread, self).__init__()
self.logger = getLogger(type(self).__name__)
self.core = core
self.signals = self.Signals()
self.logger = getLogger(type(self).__name__)
self.args = args
self.rgame = rgame
self.sync_action = sync_action
def run(self) -> None:
self.logger.info(f"Sync action: {self.sync_action}")
if self.sync_action == CloudSaveDialog.UPLOAD:
if self.sync_action == CloudSyncDialogResult.UPLOAD:
self.rgame.upload_saves(False)
elif self.sync_action == CloudSaveDialog.DOWNLOAD:
elif self.sync_action == CloudSyncDialogResult.DOWNLOAD:
self.rgame.download_saves(False)
else:
self.logger.info("No sync action")
args = self.prepare_launch(self.args)
if not args:
if args := self.prepare_launch(self.args):
self.signals.ready_to_launch.emit(args)
else:
return
self.signals.ready_to_launch.emit(args)
def prepare_launch(self, args: InitArgs) -> Optional[LaunchArgs]:
try:
launch_args = get_launch_args(self.core, args)
launch_args = get_launch_args(self.rgame, args)
except Exception as e:
self.signals.error_occurred.emit(str(e))
return None
@ -121,7 +124,7 @@ class RareLauncher(RareApp):
def __init__(self, args: InitArgs):
super(RareLauncher, self).__init__(args, f"{type(self).__name__}_{args.app_name}_{{0}}.log")
self.socket: Optional[QLocalSocket] = None
self.console: Optional[Console] = None
self.console: Optional[ConsoleDialog] = None
self.game_process: QProcess = QProcess(self)
self.server: QLocalServer = QLocalServer(self)
@ -142,7 +145,7 @@ class RareLauncher(RareApp):
self.load_translator(lang)
if QSettings().value("show_console", False, bool):
self.console = Console()
self.console = ConsoleDialog()
self.console.show()
self.game_process.finished.connect(self.__process_finished)
@ -213,15 +216,22 @@ class RareLauncher(RareApp):
state, (dt_local, dt_remote) = self.rgame.save_game_state
if state == SaveGameStatus.LOCAL_NEWER and not self.no_sync_on_exit:
action = CloudSaveDialog.UPLOAD
action = CloudSyncDialogResult.UPLOAD
self.__check_saved_finished(exit_code, action)
else:
action = CloudSaveDialog(self.rgame.igame, dt_local, dt_remote).get_action()
sync_dialog = CloudSyncDialog(self.rgame.igame, dt_local, dt_remote)
sync_dialog.result_ready.connect(lambda a: self.__check_saved_finished(exit_code, a))
sync_dialog.open()
if action == CloudSaveDialog.UPLOAD:
@pyqtSlot(int, int)
@pyqtSlot(int, CloudSyncDialogResult)
def __check_saved_finished(self, exit_code, action):
action = CloudSyncDialogResult(action)
if action == CloudSyncDialogResult.UPLOAD:
if self.console:
self.console.log("Uploading saves...")
self.rgame.upload_saves()
elif action == CloudSaveDialog.DOWNLOAD:
elif action == CloudSyncDialogResult.DOWNLOAD:
if self.console:
self.console.log("Downloading saves...")
self.rgame.download_saves()
@ -326,14 +336,20 @@ class RareLauncher(RareApp):
return
_, (dt_local, dt_remote) = self.rgame.save_game_state
dlg = CloudSaveDialog(self.rgame.igame, dt_local, dt_remote)
action = dlg.get_action()
if action == CloudSaveDialog.CANCEL:
sync_dialog = CloudSyncDialog(self.rgame.igame, dt_local, dt_remote)
sync_dialog.result_ready.connect(self.__sync_ready)
sync_dialog.open()
@pyqtSlot(int)
@pyqtSlot(CloudSyncDialogResult)
def __sync_ready(self, action: CloudSyncDialogResult):
action = CloudSyncDialogResult(action)
if action == CloudSyncDialogResult.CANCEL:
self.no_sync_on_exit = True
if self.console:
if action == CloudSaveDialog.DOWNLOAD:
if action == CloudSyncDialogResult.DOWNLOAD:
self.console.log("Downloading saves...")
elif action == CloudSaveDialog.UPLOAD:
elif action == CloudSyncDialogResult.UPLOAD:
self.console.log("Uploading saves...")
self.start_prepare(action)

View file

@ -0,0 +1,103 @@
import sys
from datetime import datetime
from enum import IntEnum
from logging import getLogger
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QDialog
from legendary.core import LegendaryCore
from legendary.models.game import InstalledGame
from rare.ui.components.tabs.games.game_info.cloud_sync_widget import Ui_CloudSyncWidget
from rare.utils.misc import icon
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
logger = getLogger("CloudSyncDialog")
class CloudSyncDialogResult(IntEnum):
DOWNLOAD = 2
UPLOAD = 1
CANCEL = 0
SKIP = 3
class CloudSyncDialog(ButtonDialog):
result_ready: pyqtSignal = pyqtSignal(CloudSyncDialogResult)
def __init__(self, igame: InstalledGame, dt_local: datetime, dt_remote: datetime, parent=None):
super(CloudSyncDialog, self).__init__(parent=parent)
header = self.tr("Cloud saves for")
self.setWindowTitle(dialog_title_game(header, igame.title))
title_label = QLabel(f"<h4>{dialog_title_game(header, igame.title)}</h4>", self)
sync_widget = QWidget(self)
self.sync_ui = Ui_CloudSyncWidget()
self.sync_ui.setupUi(sync_widget)
layout = QVBoxLayout()
layout.addWidget(title_label)
layout.addWidget(sync_widget)
self.accept_button.setText(self.tr("Skip"))
self.accept_button.setIcon(icon("fa.chevron-right"))
self.setCentralLayout(layout)
self.status = CloudSyncDialogResult.CANCEL
newer = self.tr("Newer")
if dt_remote and dt_local:
self.sync_ui.age_label_local.setText(f"<b>{newer}</b>" if dt_remote < dt_local else " ")
self.sync_ui.age_label_remote.setText(f"<b>{newer}</b>" if dt_remote > dt_local else " ")
# Set status, if one of them is None
elif dt_remote and not dt_local:
self.status = CloudSyncDialogResult.DOWNLOAD
elif not dt_remote and dt_local:
self.status = CloudSyncDialogResult.UPLOAD
else:
self.status = CloudSyncDialogResult.SKIP
self.sync_ui.date_info_local.setText(dt_local.strftime("%A, %d. %B %Y %X") if dt_local else "None")
self.sync_ui.date_info_remote.setText(dt_remote.strftime("%A, %d. %B %Y %X") if dt_remote else "None")
self.sync_ui.icon_local.setPixmap(icon("mdi.harddisk", "fa.desktop").pixmap(128, 128))
self.sync_ui.icon_remote.setPixmap(icon("mdi.cloud-outline", "ei.cloud").pixmap(128, 128))
self.sync_ui.upload_button.clicked.connect(self.__on_upload)
self.sync_ui.download_button.clicked.connect(self.__on_download)
if self.status == CloudSyncDialogResult.SKIP:
self.accept()
def __on_upload(self):
self.status = CloudSyncDialogResult.UPLOAD
self.done(QDialog.Accepted)
def __on_download(self):
self.status = CloudSyncDialogResult.DOWNLOAD
self.done(QDialog.Accepted)
def done_handler(self):
self.result_ready.emit(self.status)
def accept_handler(self):
self.status = CloudSyncDialogResult.SKIP
def reject_handler(self):
self.status = CloudSyncDialogResult.CANCEL
if __name__ == "__main__":
app = QApplication(sys.argv)
core = LegendaryCore()
@pyqtSlot(int)
def __callback(status: int):
print(repr(CloudSyncDialogResult(status)))
dlg = CloudSyncDialog(core.get_installed_list()[0], datetime.now(), datetime.strptime("2021,1", "%Y,%M"))
dlg.result_ready.connect(__callback)
dlg.open()
app.exec()

View file

@ -14,16 +14,16 @@ from PyQt5.QtWidgets import (
QSizePolicy, QTableWidgetItem, QHeaderView, QApplication,
)
from rare.ui.components.extra.console_env import Ui_ConsoleEnv
from rare.ui.launcher.console_env import Ui_ConsoleEnv
class Console(QDialog):
class ConsoleDialog(QDialog):
term = pyqtSignal()
kill = pyqtSignal()
env: QProcessEnvironment
def __init__(self, parent=None):
super(Console, self).__init__(parent=parent)
super(ConsoleDialog, self).__init__(parent=parent)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowTitle("Rare - Console")
self.setGeometry(0, 0, 640, 480)
@ -68,7 +68,7 @@ class Console(QDialog):
self.accept_close = False
def show(self) -> None:
super(Console, self).show()
super(ConsoleDialog, self).show()
self.center_window()
def center_window(self):
@ -131,7 +131,7 @@ class Console(QDialog):
def closeEvent(self, a0: QCloseEvent) -> None:
if self.accept_close:
super(Console, self).closeEvent(a0)
super(ConsoleDialog, self).closeEvent(a0)
a0.accept()
else:
self.showMinimized()

View file

@ -7,10 +7,9 @@ from logging import getLogger
from typing import List
from PyQt5.QtCore import QProcess, QProcessEnvironment
from legendary.models.game import InstalledGame, LaunchParameters
from rare.lgndr.core import LegendaryCore
from legendary.models.game import LaunchParameters
from rare.models.base_game import RareGameSlim
logger = getLogger("Helper")
@ -55,9 +54,11 @@ class LaunchArgs:
return bool(self.executable)
def get_origin_params(core: LegendaryCore, app_name, offline: bool,
launch_args: LaunchArgs) -> LaunchArgs:
origin_uri = core.get_origin_uri(app_name, offline)
def get_origin_params(rgame: RareGameSlim, init_args: InitArgs, launch_args: LaunchArgs) -> LaunchArgs:
core = rgame.core
app_name = rgame.app_name
origin_uri = core.get_origin_uri(app_name, init_args.offline)
if platform.system() == "Windows":
launch_args.executable = origin_uri
launch_args.arguments = []
@ -75,8 +76,7 @@ def get_origin_params(core: LegendaryCore, app_name, offline: bool,
if os.environ.get("container") == "flatpak":
flatpak_command = ["flatpak-spawn", "--host"]
for name, value in env.items():
flatpak_command.append(f"--env={name}={value}")
flatpak_command.extend(f"--env={name}={value}" for name, value in env.items())
command = flatpak_command + command
else:
for name, value in env.items():
@ -88,24 +88,25 @@ def get_origin_params(core: LegendaryCore, app_name, offline: bool,
return launch_args
def get_game_params(core: LegendaryCore, igame: InstalledGame, args: InitArgs,
launch_args: LaunchArgs) -> LaunchArgs:
def get_game_params(rgame: RareGameSlim, args: InitArgs, launch_args: LaunchArgs) -> LaunchArgs:
if not args.offline: # skip for update
if not args.skip_update_check and not core.is_noupdate_game(igame.app_name):
# print("Checking for updates...")
# check updates
if not args.skip_update_check and not rgame.core.is_noupdate_game(rgame.app_name):
try:
latest = core.get_asset(
igame.app_name, igame.platform, update=False
)
latest = rgame.core.get_asset(rgame.app_name, rgame.igame.platform, update=False)
except ValueError:
raise GameArgsError("Metadata doesn't exist")
else:
if latest.build_version != igame.version:
if latest.build_version != rgame.igame.version:
raise GameArgsError("Game is not up to date. Please update first")
params: LaunchParameters = core.get_launch_parameters(
app_name=igame.app_name, offline=args.offline
if (not rgame.igame or not rgame.igame.executable) and rgame.game is not None:
# override installed game with base title
if rgame.is_launchable_addon:
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
)
full_params = []
@ -113,8 +114,10 @@ def get_game_params(core: LegendaryCore, igame: InstalledGame, args: InitArgs,
if os.environ.get("container") == "flatpak":
full_params.extend(["flatpak-spawn", "--host"])
for name, value in params.environment.items():
full_params.append(f"--env={name}={value}")
full_params.extend(
f"--env={name}={value}"
for name, value in params.environment.items()
)
else:
for name, value in params.environment.items():
launch_args.environment.insert(name, value)
@ -134,32 +137,30 @@ def get_game_params(core: LegendaryCore, igame: InstalledGame, args: InitArgs,
return launch_args
def get_launch_args(core: LegendaryCore, args: InitArgs = None) -> LaunchArgs:
game = core.get_game(args.app_name)
igame = core.get_installed_game(args.app_name)
def get_launch_args(rgame: RareGameSlim, init_args: InitArgs = None) -> LaunchArgs:
resp = LaunchArgs()
if not game:
raise GameArgsError(f"Could not find metadata for ")
if not rgame.game:
raise GameArgsError(f"Could not find metadata for {rgame.app_title}")
if game.third_party_store == "Origin":
args.offline = False
if rgame.is_origin:
init_args.offline = False
else:
if not igame:
if not rgame.is_installed:
raise GameArgsError("Game is not installed or has unsupported format")
if game.is_dlc:
if rgame.is_dlc:
raise GameArgsError("Game is a DLC")
if not os.path.exists(igame.install_path):
if not os.path.exists(rgame.install_path):
raise GameArgsError("Game path does not exist")
if game.third_party_store == "Origin":
resp = get_origin_params(core, args.app_name, args.offline, resp)
if rgame.is_origin:
resp = get_origin_params(rgame, init_args, resp)
else:
resp = get_game_params(core, igame, args, resp)
resp = get_game_params(rgame, init_args, resp)
pre_cmd, wait = core.get_pre_launch_command(args.app_name)
pre_cmd, wait = rgame.core.get_pre_launch_command(init_args.app_name)
resp.pre_launch_command, resp.pre_launch_wait = pre_cmd, wait
return resp

View file

@ -97,7 +97,7 @@ def main() -> int:
print(f"Rare {__version__} Codename: {__codename__}")
return 0
if args.subparser == "start" or args.subparser == "launch":
if args.subparser in {"start", "launch"}:
from rare.launcher import launch
return launch(args)

View file

@ -154,14 +154,30 @@ class RareGameBase(QObject):
@return bool If the game is an Origin game
"""
return (
self.game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin"
)
return self.game.third_party_store in {"Origin", "the EA app"}
@property
def is_overlay(self):
return self.app_name == eos.EOSOverlayApp.app_name
@property
def is_dlc(self) -> bool:
"""!
@brief Property to report if Game is a dlc
@return bool
"""
return self.game.is_dlc
@property
def is_launchable_addon(self) -> bool:
# lk: the attribute doesn't exist in the currently released version
# FIXME: remove after legendary >= 0.20.35
try:
return self.game.is_launchable_addon
except AttributeError:
return False
@property
def version(self) -> str:
"""!
@ -192,7 +208,9 @@ class RareGameSlim(RareGameBase):
@property
def is_installed(self) -> bool:
return True
if self.is_origin:
return True
return self.igame is not None
def set_installed(self, installed: bool) -> None:
pass
@ -300,8 +318,7 @@ class RareGameSlim(RareGameBase):
@property
def is_save_up_to_date(self):
status, (_, _) = self.save_game_state
return (status == SaveGameStatus.SAME_AGE) \
or (status == SaveGameStatus.NO_SAVE)
return status in {SaveGameStatus.SAME_AGE, SaveGameStatus.NO_SAVE}
@property
def raw_save_path(self) -> str:

View file

@ -376,15 +376,6 @@ class RareGame(RareGameSlim):
if not needs and os.path.exists(self.repair_file):
os.unlink(self.repair_file)
@property
def is_dlc(self) -> bool:
"""!
@brief Property to report if Game is a dlc
@return bool
"""
return self.game.is_dlc
@property
def is_unreal(self) -> bool:
"""!
@ -411,9 +402,7 @@ class RareGame(RareGameSlim):
@property
def is_ubisoft(self) -> bool:
return (
self.game.metadata.get("customAttributes", {}).get("partnerLinkType", {}).get("value") == "ubisoft"
)
return self.game.partner_link_type == "ubisoft"
@property
def folder_name(self) -> str:

View file

@ -117,3 +117,18 @@ class UninstallOptionsModel:
self.accepted = values[0]
self.keep_files = values[1]
self.keep_config = values[2]
@dataclass
class SelectiveDownloadsModel:
app_name: str
accepted: bool = None
install_tag: Optional[List[str]] = None
def __bool__(self):
return (
bool(self.app_name)
and (self.accepted is not None)
and (self.install_tag is not None)
)

View file

@ -2,7 +2,7 @@ import json
import logging
from enum import IntEnum
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, pyqtSlot
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, pyqtSlot, Qt
from PyQt5.QtNetwork import QLocalSocket
from PyQt5.QtWidgets import QMessageBox
from legendary.models.game import Game

View file

@ -374,7 +374,7 @@ class RareCore(QObject):
@property
def games(self) -> Iterator[RareGame]:
return self.__filter_games(lambda game: not game.is_dlc)
return self.__filter_games(lambda game: not game.is_dlc or game.is_launchable_addon)
@property
def installed_games(self) -> Iterator[RareGame]:

View file

@ -14,20 +14,20 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_InstallDialog(object):
def setupUi(self, InstallDialog):
InstallDialog.setObjectName("InstallDialog")
InstallDialog.resize(272, 238)
InstallDialog.resize(179, 204)
InstallDialog.setWindowTitle("InstallDialog")
self.install_dialog_layout = QtWidgets.QFormLayout(InstallDialog)
self.install_dialog_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.install_dialog_layout.setObjectName("install_dialog_layout")
self.install_dialog_label = QtWidgets.QLabel(InstallDialog)
self.install_dialog_label.setObjectName("install_dialog_label")
self.install_dialog_layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.install_dialog_label)
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.install_dialog_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.install_dir_label)
self.main_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.install_dir_label)
self.platform_label = QtWidgets.QLabel(InstallDialog)
self.platform_label.setObjectName("platform_label")
self.install_dialog_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.platform_label)
self.main_layout.setWidget(2, 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 +35,54 @@ class Ui_InstallDialog(object):
sizePolicy.setHeightForWidth(self.platform_combo.sizePolicy().hasHeightForWidth())
self.platform_combo.setSizePolicy(sizePolicy)
self.platform_combo.setObjectName("platform_combo")
self.install_dialog_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.platform_combo)
self.main_layout.setWidget(2, 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.install_dialog_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.shortcut_label)
self.main_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.shortcut_label)
self.shortcut_check = QtWidgets.QCheckBox(InstallDialog)
self.shortcut_check.setText("")
self.shortcut_check.setObjectName("shortcut_check")
self.install_dialog_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.shortcut_check)
self.main_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.shortcut_check)
self.selectable_layout = QtWidgets.QVBoxLayout()
self.selectable_layout.setObjectName("selectable_layout")
self.install_dialog_layout.setLayout(4, QtWidgets.QFormLayout.SpanningRole, self.selectable_layout)
self.main_layout.setLayout(4, QtWidgets.QFormLayout.SpanningRole, self.selectable_layout)
self.advanced_layout = QtWidgets.QVBoxLayout()
self.advanced_layout.setObjectName("advanced_layout")
self.install_dialog_layout.setLayout(5, QtWidgets.QFormLayout.SpanningRole, self.advanced_layout)
self.main_layout.setLayout(5, QtWidgets.QFormLayout.SpanningRole, self.advanced_layout)
self.download_size_label = QtWidgets.QLabel(InstallDialog)
self.download_size_label.setObjectName("download_size_label")
self.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.download_size_label)
self.main_layout.setWidget(6, 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.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.download_size_text)
self.main_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.download_size_text)
self.install_size_label = QtWidgets.QLabel(InstallDialog)
self.install_size_label.setObjectName("install_size_label")
self.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.install_size_label)
self.main_layout.setWidget(7, 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.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.install_size_text)
self.main_layout.setWidget(7, 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.avail_space = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
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.warning_label = QtWidgets.QLabel(InstallDialog)
self.warning_label.setObjectName("warning_label")
self.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.warning_label)
self.main_layout.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.warning_label)
self.warning_text = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setItalic(True)
@ -81,57 +92,29 @@ 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.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.warning_text)
self.button_layout = QtWidgets.QHBoxLayout()
self.button_layout.setObjectName("button_layout")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.button_layout.addItem(spacerItem)
self.cancel_button = QtWidgets.QPushButton(InstallDialog)
self.cancel_button.setObjectName("cancel_button")
self.button_layout.addWidget(self.cancel_button)
self.verify_button = QtWidgets.QPushButton(InstallDialog)
self.verify_button.setObjectName("verify_button")
self.button_layout.addWidget(self.verify_button)
self.install_button = QtWidgets.QPushButton(InstallDialog)
self.install_button.setObjectName("install_button")
self.button_layout.addWidget(self.install_button)
self.install_dialog_layout.setLayout(10, QtWidgets.QFormLayout.SpanningRole, self.button_layout)
self.avail_space_lbl = QtWidgets.QLabel(InstallDialog)
self.avail_space_lbl.setObjectName("avail_space_lbl")
self.install_dialog_layout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.avail_space_lbl)
self.avail_space = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.avail_space.setFont(font)
self.avail_space.setText("")
self.avail_space.setObjectName("avail_space")
self.install_dialog_layout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.avail_space)
self.main_layout.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.warning_text)
self.retranslateUi(InstallDialog)
def retranslateUi(self, InstallDialog):
_translate = QtCore.QCoreApplication.translate
self.install_dialog_label.setText(_translate("InstallDialog", "error"))
self.install_dir_label.setText(_translate("InstallDialog", "Install directory"))
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"))
self.download_size_label.setText(_translate("InstallDialog", "Download size"))
self.download_size_text.setText(_translate("InstallDialog", "Click verify..."))
self.install_size_label.setText(_translate("InstallDialog", "Total install size"))
self.install_size_text.setText(_translate("InstallDialog", "Click verify..."))
self.avail_space_label.setText(_translate("InstallDialog", "Available space"))
self.warning_label.setText(_translate("InstallDialog", "Warning"))
self.warning_text.setText(_translate("InstallDialog", "None"))
self.cancel_button.setText(_translate("InstallDialog", "Cancel"))
self.verify_button.setText(_translate("InstallDialog", "Verify"))
self.install_button.setText(_translate("InstallDialog", "Install"))
self.avail_space_lbl.setText(_translate("InstallDialog", "Available space"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
InstallDialog = QtWidgets.QDialog()
InstallDialog = QtWidgets.QWidget()
ui = Ui_InstallDialog()
ui.setupUi(InstallDialog)
InstallDialog.show()

View file

@ -1,24 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InstallDialog</class>
<widget class="QDialog" name="InstallDialog">
<widget class="QWidget" name="InstallDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>272</width>
<height>238</height>
<width>179</width>
<height>204</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">InstallDialog</string>
</property>
<layout class="QFormLayout" name="install_dialog_layout">
<layout class="QFormLayout" name="main_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="install_dialog_label">
<widget class="QLabel" name="title_label">
<property name="text">
<string>error</string>
</property>
@ -27,7 +27,7 @@
<item row="1" column="0">
<widget class="QLabel" name="install_dir_label">
<property name="text">
<string>Install directory</string>
<string>Install folder</string>
</property>
</widget>
</item>
@ -112,6 +112,26 @@
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="avail_space_label">
<property name="text">
<string>Available space</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="avail_space">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="warning_label">
<property name="text">
@ -143,64 +163,6 @@
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<layout class="QHBoxLayout" name="button_layout">
<item>
<spacer name="button_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="cancel_button">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="verify_button">
<property name="text">
<string>Verify</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="install_button">
<property name="text">
<string>Install</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="avail_space_lbl">
<property name="text">
<string>Available space</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="avail_space">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View file

@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'rare/ui/components/dialogs/sync_save_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_SyncSaveDialog(object):
def setupUi(self, SyncSaveDialog):
SyncSaveDialog.setObjectName("SyncSaveDialog")
SyncSaveDialog.resize(648, 394)
self.verticalLayout = QtWidgets.QVBoxLayout(SyncSaveDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.title_label = QtWidgets.QLabel(SyncSaveDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.title_label.sizePolicy().hasHeightForWidth())
self.title_label.setSizePolicy(sizePolicy)
self.title_label.setObjectName("title_label")
self.verticalLayout.addWidget(self.title_label)
self.sync_widget_layout = QtWidgets.QHBoxLayout()
self.sync_widget_layout.setObjectName("sync_widget_layout")
self.verticalLayout.addLayout(self.sync_widget_layout)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.cancel_button = QtWidgets.QPushButton(SyncSaveDialog)
self.cancel_button.setObjectName("cancel_button")
self.horizontalLayout_2.addWidget(self.cancel_button)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.retranslateUi(SyncSaveDialog)
def retranslateUi(self, SyncSaveDialog):
_translate = QtCore.QCoreApplication.translate
SyncSaveDialog.setWindowTitle(_translate("SyncSaveDialog", "Sync saves with cloud"))
self.title_label.setText(_translate("SyncSaveDialog", "Select save, you want to use for "))
self.cancel_button.setText(_translate("SyncSaveDialog", "Cancel"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
SyncSaveDialog = QtWidgets.QDialog()
ui = Ui_SyncSaveDialog()
ui.setupUi(SyncSaveDialog)
SyncSaveDialog.show()
sys.exit(app.exec_())

View file

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SyncSaveDialog</class>
<widget class="QDialog" name="SyncSaveDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>648</width>
<height>394</height>
</rect>
</property>
<property name="windowTitle">
<string>Sync saves with cloud</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="title_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Select save, you want to use for </string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="sync_widget_layout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<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="cancel_button">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'rare/ui/components/tabs/games/game_info/cloud_settings_widget.ui'
#
# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_CloudSettingsWidget(object):
def setupUi(self, CloudSettingsWidget):
CloudSettingsWidget.setObjectName("CloudSettingsWidget")
CloudSettingsWidget.resize(388, 78)
CloudSettingsWidget.setWindowTitle("CloudSettingsWidget")
self.main_layout = QtWidgets.QFormLayout(CloudSettingsWidget)
self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.main_layout.setObjectName("main_layout")
self.sync_label = QtWidgets.QLabel(CloudSettingsWidget)
self.sync_label.setObjectName("sync_label")
self.main_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.sync_label)
self.sync_check = QtWidgets.QCheckBox(CloudSettingsWidget)
font = QtGui.QFont()
font.setItalic(True)
self.sync_check.setFont(font)
self.sync_check.setText("Automatically synchronize saves with the cloud")
self.sync_check.setObjectName("sync_check")
self.main_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.sync_check)
self.path_label = QtWidgets.QLabel(CloudSettingsWidget)
self.path_label.setObjectName("path_label")
self.main_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.path_label)
self.retranslateUi(CloudSettingsWidget)
def retranslateUi(self, CloudSettingsWidget):
_translate = QtCore.QCoreApplication.translate
CloudSettingsWidget.setTitle(_translate("CloudSettingsWidget", "Settings"))
self.sync_label.setText(_translate("CloudSettingsWidget", "Enable sync"))
self.path_label.setText(_translate("CloudSettingsWidget", "Saves path"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
CloudSettingsWidget = QtWidgets.QGroupBox()
ui = Ui_CloudSettingsWidget()
ui.setupUi(CloudSettingsWidget)
CloudSettingsWidget.show()
sys.exit(app.exec_())

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CloudSettingsWidget</class>
<widget class="QGroupBox" name="CloudSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>388</width>
<height>78</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">CloudSettingsWidget</string>
</property>
<property name="title">
<string>Settings</string>
</property>
<layout class="QFormLayout" name="main_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="sync_label">
<property name="text">
<string>Enable sync</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="sync_check">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string notr="true">Automatically synchronize saves with the cloud</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="path_label">
<property name="text">
<string>Saves path</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'rare/ui/components/tabs/games/game_info/sync_widget.ui'
# Form implementation generated from reading ui file 'rare/ui/components/tabs/games/game_info/cloud_sync_widget.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@ -11,29 +11,29 @@
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_SyncWidget(object):
def setupUi(self, SyncWidget):
SyncWidget.setObjectName("SyncWidget")
SyncWidget.resize(438, 137)
class Ui_CloudSyncWidget(object):
def setupUi(self, CloudSyncWidget):
CloudSyncWidget.setObjectName("CloudSyncWidget")
CloudSyncWidget.resize(438, 137)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(SyncWidget.sizePolicy().hasHeightForWidth())
SyncWidget.setSizePolicy(sizePolicy)
SyncWidget.setWindowTitle("SyncWidget")
self.sync_layout = QtWidgets.QHBoxLayout(SyncWidget)
self.sync_layout.setContentsMargins(0, 0, 0, 0)
self.sync_layout.setObjectName("sync_layout")
self.local_gb = QtWidgets.QGroupBox(SyncWidget)
self.local_gb.setObjectName("local_gb")
self.local_layout = QtWidgets.QVBoxLayout(self.local_gb)
sizePolicy.setHeightForWidth(CloudSyncWidget.sizePolicy().hasHeightForWidth())
CloudSyncWidget.setSizePolicy(sizePolicy)
CloudSyncWidget.setWindowTitle("SyncWidget")
self.main_layout = QtWidgets.QHBoxLayout(CloudSyncWidget)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.setObjectName("main_layout")
self.local_group = QtWidgets.QGroupBox(CloudSyncWidget)
self.local_group.setObjectName("local_group")
self.local_layout = QtWidgets.QVBoxLayout(self.local_group)
self.local_layout.setObjectName("local_layout")
self.date_info_local = QtWidgets.QLabel(self.local_gb)
self.date_info_local = QtWidgets.QLabel(self.local_group)
self.date_info_local.setText("")
self.date_info_local.setAlignment(QtCore.Qt.AlignCenter)
self.date_info_local.setObjectName("date_info_local")
self.local_layout.addWidget(self.date_info_local)
self.icon_local = QtWidgets.QLabel(self.local_gb)
self.icon_local = QtWidgets.QLabel(self.local_group)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -43,26 +43,26 @@ class Ui_SyncWidget(object):
self.icon_local.setAlignment(QtCore.Qt.AlignCenter)
self.icon_local.setObjectName("icon_local")
self.local_layout.addWidget(self.icon_local)
self.age_label_local = QtWidgets.QLabel(self.local_gb)
self.age_label_local = QtWidgets.QLabel(self.local_group)
self.age_label_local.setText("")
self.age_label_local.setAlignment(QtCore.Qt.AlignCenter)
self.age_label_local.setObjectName("age_label_local")
self.local_layout.addWidget(self.age_label_local)
self.upload_button = QtWidgets.QPushButton(self.local_gb)
self.upload_button = QtWidgets.QPushButton(self.local_group)
self.upload_button.setMinimumSize(QtCore.QSize(192, 0))
self.upload_button.setObjectName("upload_button")
self.local_layout.addWidget(self.upload_button)
self.sync_layout.addWidget(self.local_gb)
self.cloud_gb = QtWidgets.QGroupBox(SyncWidget)
self.cloud_gb.setObjectName("cloud_gb")
self.cloud_layout = QtWidgets.QVBoxLayout(self.cloud_gb)
self.main_layout.addWidget(self.local_group)
self.cloud_group = QtWidgets.QGroupBox(CloudSyncWidget)
self.cloud_group.setObjectName("cloud_group")
self.cloud_layout = QtWidgets.QVBoxLayout(self.cloud_group)
self.cloud_layout.setObjectName("cloud_layout")
self.date_info_remote = QtWidgets.QLabel(self.cloud_gb)
self.date_info_remote = QtWidgets.QLabel(self.cloud_group)
self.date_info_remote.setText("")
self.date_info_remote.setAlignment(QtCore.Qt.AlignCenter)
self.date_info_remote.setObjectName("date_info_remote")
self.cloud_layout.addWidget(self.date_info_remote)
self.icon_remote = QtWidgets.QLabel(self.cloud_gb)
self.icon_remote = QtWidgets.QLabel(self.cloud_group)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -72,32 +72,32 @@ class Ui_SyncWidget(object):
self.icon_remote.setAlignment(QtCore.Qt.AlignCenter)
self.icon_remote.setObjectName("icon_remote")
self.cloud_layout.addWidget(self.icon_remote)
self.age_label_remote = QtWidgets.QLabel(self.cloud_gb)
self.age_label_remote = QtWidgets.QLabel(self.cloud_group)
self.age_label_remote.setText("")
self.age_label_remote.setAlignment(QtCore.Qt.AlignCenter)
self.age_label_remote.setObjectName("age_label_remote")
self.cloud_layout.addWidget(self.age_label_remote)
self.download_button = QtWidgets.QPushButton(self.cloud_gb)
self.download_button = QtWidgets.QPushButton(self.cloud_group)
self.download_button.setMinimumSize(QtCore.QSize(192, 0))
self.download_button.setObjectName("download_button")
self.cloud_layout.addWidget(self.download_button)
self.sync_layout.addWidget(self.cloud_gb)
self.main_layout.addWidget(self.cloud_group)
self.retranslateUi(SyncWidget)
self.retranslateUi(CloudSyncWidget)
def retranslateUi(self, SyncWidget):
def retranslateUi(self, CloudSyncWidget):
_translate = QtCore.QCoreApplication.translate
self.local_gb.setTitle(_translate("SyncWidget", "Local"))
self.upload_button.setText(_translate("SyncWidget", "Upload"))
self.cloud_gb.setTitle(_translate("SyncWidget", "Cloud"))
self.download_button.setText(_translate("SyncWidget", "Download"))
self.local_group.setTitle(_translate("CloudSyncWidget", "Local"))
self.upload_button.setText(_translate("CloudSyncWidget", "Upload"))
self.cloud_group.setTitle(_translate("CloudSyncWidget", "Cloud"))
self.download_button.setText(_translate("CloudSyncWidget", "Download"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
SyncWidget = QtWidgets.QWidget()
ui = Ui_SyncWidget()
ui.setupUi(SyncWidget)
SyncWidget.show()
CloudSyncWidget = QtWidgets.QWidget()
ui = Ui_CloudSyncWidget()
ui.setupUi(CloudSyncWidget)
CloudSyncWidget.show()
sys.exit(app.exec_())

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SyncWidget</class>
<widget class="QWidget" name="SyncWidget">
<class>CloudSyncWidget</class>
<widget class="QWidget" name="CloudSyncWidget">
<property name="geometry">
<rect>
<x>0</x>
@ -19,7 +19,7 @@
<property name="windowTitle">
<string notr="true">SyncWidget</string>
</property>
<layout class="QHBoxLayout" name="sync_layout">
<layout class="QHBoxLayout" name="main_layout">
<property name="leftMargin">
<number>0</number>
</property>
@ -33,7 +33,7 @@
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="local_gb">
<widget class="QGroupBox" name="local_group">
<property name="title">
<string>Local</string>
</property>
@ -91,7 +91,7 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="cloud_gb">
<widget class="QGroupBox" name="cloud_group">
<property name="title">
<string>Cloud</string>
</property>

View file

@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'rare/ui/components/tabs/games/game_info/cloud_widget.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_CloudWidget(object):
def setupUi(self, CloudWidget):
CloudWidget.setObjectName("CloudWidget")
CloudWidget.resize(251, 93)
CloudWidget.setWindowTitle("GroupBox")
self.cloud_layout = QtWidgets.QFormLayout(CloudWidget)
self.cloud_layout.setObjectName("cloud_layout")
self.cloud_sync_label = QtWidgets.QLabel(CloudWidget)
self.cloud_sync_label.setObjectName("cloud_sync_label")
self.cloud_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.cloud_sync_label)
self.cloud_sync = QtWidgets.QCheckBox(CloudWidget)
self.cloud_sync.setText("")
self.cloud_sync.setObjectName("cloud_sync")
self.cloud_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cloud_sync)
self.retranslateUi(CloudWidget)
def retranslateUi(self, CloudWidget):
_translate = QtCore.QCoreApplication.translate
CloudWidget.setTitle(_translate("CloudWidget", "Options"))
self.cloud_sync_label.setText(_translate("CloudWidget", "Sync with cloud"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
CloudWidget = QtWidgets.QGroupBox()
ui = Ui_CloudWidget()
ui.setupUi(CloudWidget)
CloudWidget.show()
sys.exit(app.exec_())

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CloudWidget</class>
<widget class="QGroupBox" name="CloudWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>251</width>
<height>93</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">GroupBox</string>
</property>
<property name="title">
<string>Options</string>
</property>
<layout class="QFormLayout" name="cloud_layout">
<item row="0" column="0">
<widget class="QLabel" name="cloud_sync_label">
<property name="text">
<string>Sync with cloud</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="cloud_sync">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'rare/ui/components/tabs/games/game_info/game_info.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@ -336,13 +336,13 @@ class Ui_GameInfo(object):
self.lbl_install_path.setText(_translate("GameInfo", "Installation Path"))
self.lbl_platform.setText(_translate("GameInfo", "Platform"))
self.lbl_game_actions.setText(_translate("GameInfo", "Actions"))
self.modify_button.setText(_translate("GameInfo", "Modify Installation"))
self.verify_button.setText(_translate("GameInfo", "Verify Installation"))
self.repair_button.setText(_translate("GameInfo", "Repair Installation"))
self.move_button.setText(_translate("GameInfo", "Move Installation"))
self.uninstall_button.setText(_translate("GameInfo", "Uninstall Game"))
self.install_button.setText(_translate("GameInfo", "Install Game"))
self.import_button.setText(_translate("GameInfo", "Import Game"))
self.modify_button.setText(_translate("GameInfo", "Modify"))
self.verify_button.setText(_translate("GameInfo", "Verify"))
self.repair_button.setText(_translate("GameInfo", "Repair"))
self.move_button.setText(_translate("GameInfo", "Move"))
self.uninstall_button.setText(_translate("GameInfo", "Uninstall"))
self.install_button.setText(_translate("GameInfo", "Install"))
self.import_button.setText(_translate("GameInfo", "Import"))
if __name__ == "__main__":

View file

@ -395,7 +395,7 @@
<item>
<widget class="QPushButton" name="modify_button">
<property name="text">
<string>Modify Installation</string>
<string>Modify</string>
</property>
</widget>
</item>
@ -424,7 +424,7 @@
<item>
<widget class="QPushButton" name="verify_button">
<property name="text">
<string>Verify Installation</string>
<string>Verify</string>
</property>
</widget>
</item>
@ -461,7 +461,7 @@
<item>
<widget class="QPushButton" name="repair_button">
<property name="text">
<string>Repair Installation</string>
<string>Repair</string>
</property>
</widget>
</item>
@ -496,7 +496,7 @@
</sizepolicy>
</property>
<property name="text">
<string>Move Installation</string>
<string>Move</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
@ -542,7 +542,7 @@
<item>
<widget class="QPushButton" name="uninstall_button">
<property name="text">
<string>Uninstall Game</string>
<string>Uninstall</string>
</property>
</widget>
</item>
@ -565,14 +565,14 @@
<item>
<widget class="QPushButton" name="install_button">
<property name="text">
<string>Install Game</string>
<string>Install</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="import_button">
<property name="text">
<string>Import Game</string>
<string>Import</string>
</property>
</widget>
</item>

View file

@ -141,7 +141,7 @@ def get_rare_executable() -> List[str]:
executable = [sys.executable]
elif sys.argv[0].endswith("__main__.py"):
executable = [sys.executable, "-m", "rare"]
elif platform.system() == "Linux" or platform.system() == "Darwin" or platform.system() == "FreeBSD":
elif platform.system() in {"Linux", "FreeBSD", "Darwin"}:
if p := os.environ.get("APPIMAGE"):
executable = [p]
else:
@ -214,7 +214,7 @@ def create_desktop_link(app_name: str, app_title: str = "", link_name: str = "",
else:
logger.info(f"Creating shortcut for {app_title} at {shortcut_path}")
if platform.system() == "Linux" or platform.system() == "FreeBSD":
if platform.system() in {"Linux", "FreeBSD"}:
executable = get_rare_executable()
executable = shlex.join(executable)
if not for_rare:

View file

@ -39,11 +39,11 @@ class RareAppException(QObject):
self.logger.fatal(message)
action = QMessageBox.warning(
None, exc_type.__name__, message,
buttons=QMessageBox.Ignore | QMessageBox.Close,
defaultButton=QMessageBox.Ignore
buttons=QMessageBox.Ignore | QMessageBox.Abort,
defaultButton=QMessageBox.Abort
)
if action == QMessageBox.RejectRole:
QApplication.exit(1)
if action == QMessageBox.Abort:
QApplication.quit()
class RareApp(QApplication):
@ -85,13 +85,6 @@ class RareApp(QApplication):
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.WARNING)
self.logger.info(
f"Launching Rare version {rare.__version__} Codename: {rare.__codename__}\n"
f" - Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n"
f" - Operating System: {platform.system()}, Python version: {platform.python_version()}\n"
f" - Running {sys.executable} {' '.join(sys.argv)}\n"
f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}"
)
else:
logging.basicConfig(
format="[%(name)s] %(levelname)s: %(message)s",
@ -100,8 +93,13 @@ class RareApp(QApplication):
)
file_handler.setLevel(logging.DEBUG)
logging.root.addHandler(file_handler)
self.logger.info(f"Launching Rare version {rare.__version__}")
self.logger.info(f"Operating System: {platform.system()}")
self.logger.info(
f"Launching Rare version {rare.__version__} Codename: {rare.__codename__}\n"
f" - Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n"
f" - Operating System: {platform.system()}, Python version: {platform.python_version()}\n"
f" - Running {sys.executable} {' '.join(sys.argv)}\n"
f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}"
)
self.settings = QSettings()

View file

@ -2,8 +2,10 @@ requests
PyQt5
QtAwesome
setuptools
legendary-gl>=0.20.33
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"
pywin32; platform_system == "Windows"
pywebview[qt]; platform_system == "Linux"
pywebview[qt]; platform_system == "FreeBSD"

View file

@ -2,7 +2,8 @@ requests
PyQt5
QtAwesome
setuptools
legendary-gl>=0.20.34
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"
pywin32; platform_system == "Windows"