1
0
Fork 0
mirror of synced 2024-07-03 05:31:23 +12:00

Merge pull request #156 from Dummerle/epic_overlay

Add Settings for Epic Games Overlay. Install, enable and  disable,
This commit is contained in:
Dummerle 2022-01-25 22:03:37 +01:00 committed by GitHub
commit 363142dac7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 984 additions and 253 deletions

View file

@ -19,6 +19,7 @@ from rare import cache_dir, resources_path
from rare.components.dialogs.launch_dialog import LaunchDialog from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow from rare.components.main_window import MainWindow
from rare.components.tray_icon import TrayIcon from rare.components.tray_icon import TrayIcon
from rare.utils import legendary_utils
from rare.utils.utils import set_color_pallete, set_style_sheet from rare.utils.utils import set_color_pallete, set_style_sheet
start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute
@ -158,6 +159,16 @@ class App(QApplication):
self.mainwindow.show_window_centralized() self.mainwindow.show_window_centralized()
def start_app(self): def start_app(self):
for igame in self.core.get_installed_list():
if not os.path.exists(igame.install_path):
legendary_utils.uninstall(igame.app_name, self.core)
logger.info(f"Uninstalled {igame.title}, because no game files exist")
continue
if not os.path.exists(os.path.join(igame.install_path, igame.executable)):
igame.needs_verification = True
self.core.lgd.set_installed_game(igame.app_name, igame)
logger.info(f"{igame.title} needs verification")
self.mainwindow = MainWindow() self.mainwindow = MainWindow()
self.launch_dialog.close() self.launch_dialog.close()
self.tray_icon = TrayIcon(self) self.tray_icon = TrayIcon(self)

View file

@ -1,12 +1,15 @@
import os import os
import platform import platform
from multiprocessing import Queue as MPQueue from multiprocessing import Queue as MPQueue
from typing import Tuple
from PyQt5.QtCore import Qt, QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot from PyQt5.QtCore import Qt, QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent from PyQt5.QtGui import QCloseEvent, QKeyEvent
from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox, QMessageBox from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox, QMessageBox
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
from legendary.models.downloading import ConditionCheckResult
from legendary.models.game import Game
from legendary.utils.selective_dl import games from legendary.utils.selective_dl import games
from rare import shared from rare import shared
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
@ -30,7 +33,13 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.dl_item = dl_item self.dl_item = dl_item
self.dl_item.status_q = MPQueue() self.dl_item.status_q = MPQueue()
self.app_name = self.dl_item.options.app_name self.app_name = self.dl_item.options.app_name
self.game = self.core.get_game(self.app_name) self.game = self.core.get_game(self.app_name) \
if not self.dl_item.options.overlay \
else Game(app_name=self.app_name, app_title="Epic Overlay")
self.game_path = self.game.metadata.get('customAttributes', {}). \
get('FolderName', {}).get('value', "")
self.update = update self.update = update
self.silent = silent self.silent = silent
@ -45,12 +54,12 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.install_dialog_label.setText(f'<h3>{header} "{self.game.app_title}"</h3>') self.install_dialog_label.setText(f'<h3>{header} "{self.game.app_title}"</h3>')
self.setWindowTitle(f'{self.windowTitle()} - {header} "{self.game.app_title}"') self.setWindowTitle(f'{self.windowTitle()} - {header} "{self.game.app_title}"')
default_path = os.path.expanduser("~/legendary") if not self.dl_item.options.base_path:
if self.core.lgd.config.has_option("Legendary", "install_dir"): self.dl_item.options.base_path = shared.core.lgd.config.get("Legendary", "install_dir",
default_path = self.core.lgd.config.get("Legendary", "install_dir") fallback=os.path.expanduser("~/legendary"))
self.install_dir_edit = PathEdit( self.install_dir_edit = PathEdit(
path=default_path, path=self.dl_item.options.base_path,
file_type=QFileDialog.DirectoryOnly, file_type=QFileDialog.DirectoryOnly,
edit_func=self.option_changed, edit_func=self.option_changed,
parent=self, parent=self,
@ -125,6 +134,16 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.install_button.setEnabled(False) self.install_button.setEnabled(False)
if self.dl_item.options.overlay:
self.platform_label.setVisible(False)
self.platform_combo_box.setVisible(False)
self.ignore_space_info_label.setVisible(False)
self.ignore_space_check.setVisible(False)
self.ignore_space_label.setVisible(False)
self.download_only_check.setVisible(False)
self.download_only_info_label.setVisible(False)
self.download_only_label.setVisible(False)
self.cancel_button.clicked.connect(self.cancel_clicked) self.cancel_button.clicked.connect(self.cancel_clicked)
self.verify_button.clicked.connect(self.verify_clicked) self.verify_button.clicked.connect(self.verify_clicked)
self.install_button.clicked.connect(self.install_clicked) self.install_button.clicked.connect(self.install_clicked)
@ -144,6 +163,7 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.dl_item.options.base_path = ( self.dl_item.options.base_path = (
self.install_dir_edit.text() if not self.update else None self.install_dir_edit.text() if not self.update else None
) )
self.dl_item.options.max_workers = self.max_workers_spin.value() self.dl_item.options.max_workers = self.max_workers_spin.value()
self.dl_item.options.force = self.force_download_check.isChecked() self.dl_item.options.force = self.force_download_check.isChecked()
self.dl_item.options.ignore_space_req = self.ignore_space_check.isChecked() self.dl_item.options.ignore_space_req = self.ignore_space_check.isChecked()
@ -157,7 +177,7 @@ class InstallDialog(QDialog, Ui_InstallDialog):
def get_download_info(self): def get_download_info(self):
self.dl_item.download = None self.dl_item.download = None
info_worker = InstallInfoWorker(self.core, self.dl_item) info_worker = InstallInfoWorker(self.core, self.dl_item, self.game)
info_worker.setAutoDelete(True) info_worker.setAutoDelete(True)
info_worker.signals.result.connect(self.on_worker_result) info_worker.signals.result.connect(self.on_worker_result)
info_worker.signals.failed.connect(self.on_worker_failed) info_worker.signals.failed.connect(self.on_worker_failed)
@ -182,11 +202,16 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.get_options() self.get_options()
self.get_download_info() self.get_download_info()
def option_changed(self, path): def option_changed(self, path) -> Tuple[bool, str, str]:
self.options_changed = True self.options_changed = True
self.install_button.setEnabled(False) self.install_button.setEnabled(False)
self.verify_button.setEnabled(not self.worker_running) self.verify_button.setEnabled(not self.worker_running)
return True, path # directory is not empty
full_path = os.path.join(self.dl_item.options.base_path, self.game_path)
if not self.dl_item.options.update and os.path.exists(full_path) and len(os.listdir(full_path)) != 0:
return False, path, PathEdit.reasons.dir_not_empty
return True, path, ""
def non_reload_option_changed(self, option: str): def non_reload_option_changed(self, option: str):
if option == "download_only": if option == "download_only":
@ -250,6 +275,10 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.result_ready.emit(self.dl_item) self.result_ready.emit(self.dl_item)
a0.accept() a0.accept()
def keyPressEvent(self, e: QKeyEvent) -> None:
if e.key() == Qt.Key_Escape:
self.cancel_clicked()
class InstallInfoWorkerSignals(QObject): class InstallInfoWorkerSignals(QObject):
result = pyqtSignal(InstallDownloadModel) result = pyqtSignal(InstallDownloadModel)
@ -258,15 +287,18 @@ class InstallInfoWorkerSignals(QObject):
class InstallInfoWorker(QRunnable): class InstallInfoWorker(QRunnable):
def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel): def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel, game: Game = None):
super(InstallInfoWorker, self).__init__() super(InstallInfoWorker, self).__init__()
self.signals = InstallInfoWorkerSignals() self.signals = InstallInfoWorkerSignals()
self.core = core self.core = core
self.dl_item = dl_item self.dl_item = dl_item
self.is_overlay_install = self.dl_item.options.overlay
self.game = game
@pyqtSlot() @pyqtSlot()
def run(self): def run(self):
try: try:
if not self.is_overlay_install:
download = InstallDownloadModel( download = InstallDownloadModel(
*self.core.prepare_download( *self.core.prepare_download(
app_name=self.dl_item.options.app_name, app_name=self.dl_item.options.app_name,
@ -296,7 +328,29 @@ class InstallInfoWorker(QRunnable):
sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list, sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list,
) )
) )
if not download.res.failures:
else:
if not os.path.exists(path := self.dl_item.options.base_path):
os.makedirs(path)
dlm, analysis, igame = self.core.prepare_overlay_install(
path=self.dl_item.options.base_path,
status_queue=self.dl_item.status_q,
max_workers=self.dl_item.options.max_workers,
force=self.dl_item.options.force
)
download = InstallDownloadModel(
dlmanager=dlm,
analysis=analysis,
game=self.game,
igame=igame,
repair=False,
repair_file="",
res=ConditionCheckResult() # empty
)
if not download.res or not download.res.failures:
self.signals.result.emit(download) self.signals.result.emit(download)
else: else:
self.signals.failed.emit( self.signals.failed.emit(

View file

@ -46,19 +46,19 @@ class BrowserLogin(QWidget, Ui_BrowserLogin):
return self.sid_edit.is_valid return self.sid_edit.is_valid
@staticmethod @staticmethod
def text_changed(text) -> Tuple[bool, str]: def text_changed(text) -> Tuple[bool, str, str]:
if text: if text:
text = text.strip() text = text.strip()
if text.startswith("{") and text.endswith("}"): if text.startswith("{") and text.endswith("}"):
try: try:
text = json.loads(text).get("sid") text = json.loads(text).get("sid")
except json.JSONDecodeError: except json.JSONDecodeError:
return False, text return False, text, IndicatorLineEdit.reasons.wrong_format
elif '"' in text: elif '"' in text:
text = text.strip('"') text = text.strip('"')
return len(text) == 32, text return len(text) == 32, text, IndicatorLineEdit.reasons.wrong_format
else: else:
return False, text return False, text, ""
def do_login(self): def do_login(self):
self.status_label.setText(self.tr("Logging in...")) self.status_label.setText(self.tr("Logging in..."))

View file

@ -13,9 +13,10 @@ from PyQt5.QtWidgets import QMessageBox
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
from legendary.models.downloading import UIUpdate, WriterTask from legendary.models.downloading import UIUpdate, WriterTask
from rare import shared
from rare.utils.models import InstallQueueItemModel from rare.utils.models import InstallQueueItemModel
logger = getLogger("Download") logger = getLogger("DownloadThread")
class DownloadThread(QThread): class DownloadThread(QThread):
@ -31,6 +32,7 @@ class DownloadThread(QThread):
self.igame = queue_item.download.igame self.igame = queue_item.download.igame
self.repair = queue_item.download.repair self.repair = queue_item.download.repair
self.repair_file = queue_item.download.repair_file self.repair_file = queue_item.download.repair_file
self.queue_item = queue_item
self._kill = False self._kill = False
def run(self): def run(self):
@ -140,9 +142,15 @@ class DownloadThread(QThread):
return return
self.status.emit("dl_finished") self.status.emit("dl_finished")
end_t = time.time() end_t = time.time()
logger.info(f"Download finished in {start_time - end_t}s") logger.info(f"Download finished in {end_t - start_time}s")
game = self.core.get_game(self.igame.app_name) game = self.core.get_game(self.igame.app_name)
if self.queue_item.options.overlay:
shared.signals.overlay_installation_finished.emit()
self.core.finish_overlay_install(self.igame)
self.status.emit("finish")
return
if not self.no_install: if not self.no_install:
postinstall = self.core.install_game(self.igame) postinstall = self.core.install_game(self.igame)
if postinstall: if postinstall:

View file

@ -133,6 +133,8 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
def installation_started(self, app_name: str): def installation_started(self, app_name: str):
game = self.core.get_game(app_name, False) game = self.core.get_game(app_name, False)
if not game:
return
if game.is_dlc: if game.is_dlc:
return return
self.installing_widget.set_game(app_name) self.installing_widget.set_game(app_name)

View file

@ -88,17 +88,17 @@ class GameInfo(QWidget, Ui_GameInfo):
) )
return return
self.verify_widget.setCurrentIndex(1) self.verify_widget.setCurrentIndex(1)
verify_worker = VerifyWorker(self.core, self.game.app_name) verify_worker = VerifyWorker(self.game.app_name)
verify_worker.signals.status.connect(self.verify_staistics) verify_worker.signals.status.connect(self.verify_staistics)
verify_worker.signals.summary.connect(self.finish_verify) verify_worker.signals.summary.connect(self.finish_verify)
self.verify_progress.setValue(0) self.verify_progress.setValue(0)
self.verify_threads[self.game.app_name] = verify_worker self.verify_threads[self.game.app_name] = verify_worker
self.verify_pool.start(verify_worker) self.verify_pool.start(verify_worker)
def verify_staistics(self, progress): def verify_staistics(self, num, total, app_name):
# checked, max, app_name # checked, max, app_name
if progress[2] == self.game.app_name: if app_name == self.game.app_name:
self.verify_progress.setValue(progress[0] * 100 // progress[1]) self.verify_progress.setValue(num * 100 // total)
def finish_verify(self, failed, missing, app_name): def finish_verify(self, failed, missing, app_name):
if failed == missing == 0: if failed == missing == 0:

View file

@ -66,7 +66,7 @@ class GameSettings(QWidget, Ui_GameSettings):
"", "",
file_type=QFileDialog.DirectoryOnly, file_type=QFileDialog.DirectoryOnly,
ph_text=self.tr("Cloud save path"), ph_text=self.tr("Cloud save path"),
edit_func=lambda text: (os.path.exists(text), text), edit_func=lambda text: (os.path.exists(text), text, PathEdit.reasons.dir_not_exist),
save_func=self.save_save_path, save_func=self.save_save_path,
) )
self.cloud_gb.layout().addRow( self.cloud_gb.layout().addRow(
@ -296,12 +296,12 @@ class GameSettings(QWidget, Ui_GameSettings):
self.core.lgd.save_config() self.core.lgd.save_config()
def proton_prefix_edit(self, text: str) -> Tuple[bool, str]: def proton_prefix_edit(self, text: str) -> Tuple[bool, str, str]:
if not text: if not text:
text = os.path.expanduser("~/.proton") text = os.path.expanduser("~/.proton")
return True, text return True, text, ""
parent = os.path.dirname(text) parent = os.path.dirname(text)
return os.path.exists(parent), text return os.path.exists(parent), text, PathEdit.reasons.dir_not_exist
def proton_prefix_save(self, text: str): def proton_prefix_save(self, text: str):
self.core.lgd.config.set( self.core.lgd.config.set(

View file

@ -88,9 +88,9 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
self.egl_path_edit.setText(path) self.egl_path_edit.setText(path)
@staticmethod @staticmethod
def egl_path_edit_edit_cb(path) -> Tuple[bool, str]: def egl_path_edit_edit_cb(path) -> Tuple[bool, str, str]:
if not path: if not path:
return True, path return True, path, ""
if os.path.exists(os.path.join(path, "system.reg")) and os.path.exists( if os.path.exists(os.path.join(path, "system.reg")) and os.path.exists(
os.path.join(path, "dosdevices/c:") os.path.join(path, "dosdevices/c:")
): ):
@ -104,10 +104,10 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests" "ProgramData/Epic/EpicGamesLauncher/Data/Manifests"
): ):
# lower() might or might not be needed in the check # lower() might or might not be needed in the check
return False, path return False, path, PathEdit.reasons.wrong_path
if os.path.exists(path): if os.path.exists(path):
return True, path return True, path, ""
return False, path return False, path, PathEdit.reasons.dir_not_exist
@staticmethod @staticmethod
def egl_path_edit_save_cb(path): def egl_path_edit_save_cb(path):

View file

@ -99,13 +99,15 @@ class ImportGroup(QGroupBox, Ui_ImportGroup):
self.import_button.setEnabled(False) self.import_button.setEnabled(False)
self.import_button.clicked.connect(self.import_game) self.import_button.clicked.connect(self.import_game)
def path_edit_cb(self, path) -> Tuple[bool, str]: def path_edit_cb(self, path) -> Tuple[bool, str, str]:
if os.path.exists(path): if os.path.exists(path):
if os.path.exists(os.path.join(path, ".egstore")): if os.path.exists(os.path.join(path, ".egstore")):
return True, path return True, path, ""
elif os.path.basename(path) in self.install_dir_list: elif os.path.basename(path) in self.install_dir_list:
return True, path return True, path, ""
return False, path else:
return False, path, PathEdit.reasons.dir_not_exist
return False, path, ""
def path_changed(self, path): def path_changed(self, path):
self.info_label.setText(str()) self.info_label.setText(str())
@ -114,13 +116,13 @@ class ImportGroup(QGroupBox, Ui_ImportGroup):
else: else:
self.app_name.setText(str()) self.app_name.setText(str())
def app_name_edit_cb(self, text) -> Tuple[bool, str]: def app_name_edit_cb(self, text) -> Tuple[bool, str, str]:
if not text: if not text:
return False, text return False, text, ""
if text in self.app_name_list: if text in self.app_name_list:
return True, text return True, text, ""
else: else:
return False, text return False, text, IndicatorLineEdit.reasons.game_not_installed
def app_name_changed(self, text): def app_name_changed(self, text):
self.info_label.setText(str()) self.info_label.setText(str())

View file

@ -0,0 +1,249 @@
import os
import platform
from logging import getLogger
from typing import List
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool
from PyQt5.QtWidgets import QGroupBox, QMessageBox
from legendary.utils import eos
from rare import shared
from rare.ui.components.tabs.settings.eos_widget import Ui_EosWidget
from rare.utils.models import InstallOptionsModel
logger = getLogger("EOS")
def get_wine_prefixes() -> List[str]:
if os.path.exists(p := os.path.expanduser("~/.wine")):
prefixes = [p]
else:
prefixes = []
for i in shared.core.get_installed_list():
# get prefix from environment
env = shared.core.get_app_environment(i.app_name)
if pfx := env.get("WINEPREFIX"):
if pfx not in prefixes and os.path.exists(os.path.join(pfx, "user.reg")):
prefixes.append(pfx)
if steam_pfx := env.get("STEAM_COMPAT_DATA_PATH"):
if steam_pfx not in prefixes and os.path.exists(os.path.join(steam_pfx, "user.reg")):
prefixes.append(os.path.join(steam_pfx, "pfx"))
return prefixes
class WorkerSignals(QObject):
update_available = pyqtSignal(bool)
class CheckForUpdateWorker(QRunnable):
def __init__(self):
super(CheckForUpdateWorker, self).__init__()
self.setAutoDelete(True)
self.signals = WorkerSignals()
def run(self) -> None:
shared.core.check_for_overlay_updates()
self.signals.update_available.emit(shared.core.overlay_update_available)
class EosWidget(QGroupBox, Ui_EosWidget):
def __init__(self):
super(EosWidget, self).__init__()
self.setupUi(self)
self.core = shared.core
if platform.system() != "Windows":
self.setTitle(f"{self.title()} - {self.tr(' - This won´t work with Wine. It might work in the Future')}")
self.prefix_enabled = False
self.info_stack.addWidget(self.installed_info_gb)
self.info_stack.addWidget(self.install_overlay_gb)
self.enabled_cb.stateChanged.connect(self.change_enable)
self.uninstall_button.clicked.connect(self.uninstall_overlay)
self.update_button.setVisible(False)
self.update_info_lbl.setVisible(False)
self.overlay = self.core.lgd.get_overlay_install_info()
shared.signals.overlay_installation_finished.connect(self.overlay_installation_finished)
shared.signals.wine_prefix_updated.connect(self.update_prefixes)
self.update_check_button.clicked.connect(self.check_for_update)
self.install_button.clicked.connect(self.install_overlay)
self.update_button.clicked.connect(lambda: self.install_overlay(True))
if self.overlay: # installed
self.installed_version_lbl.setText(self.overlay.version)
self.installed_path_lbl.setText(self.overlay.install_path)
else:
self.info_stack.setCurrentIndex(1)
self.enable_gb.setDisabled(True)
if platform.system() == "Windows":
self.current_prefix = None
self.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.select_pfx_combo.addItem(pfx.replace(os.path.expanduser("~/"), "~/"))
if not pfxs:
self.enable_gb.setDisabled(True)
else:
self.select_pfx_combo.setCurrentIndex(0)
self.select_pfx_combo.currentIndexChanged.connect(self.update_select_combo)
if pfxs:
self.update_select_combo(None)
self.enabled_info_label.setText("")
self.threadpool = QThreadPool.globalInstance()
def update_prefixes(self):
logger.debug("Updated prefixes")
pfxs = get_wine_prefixes() # returns /home/whatever
self.select_pfx_combo.clear()
for pfx in pfxs:
self.select_pfx_combo.addItem(pfx.replace(os.path.expanduser("~/"), "~/"))
if self.current_prefix in pfxs:
self.select_pfx_combo.setCurrentIndex(
self.select_pfx_combo.findText(self.current_prefix.replace(os.path.expanduser("~/"), "~/")))
def check_for_update(self):
def worker_finished(update_available):
self.update_button.setVisible(update_available)
self.update_info_lbl.setVisible(update_available)
self.update_check_button.setDisabled(False)
if not update_available:
self.update_check_button.setText(self.tr("No update available"))
self.update_check_button.setDisabled(True)
worker = CheckForUpdateWorker()
worker.signals.update_available.connect(worker_finished)
QThreadPool.globalInstance().start(worker)
def overlay_installation_finished(self):
self.overlay = self.core.lgd.get_overlay_install_info()
if not self.overlay:
logger.error("Something went wrong, when installing overlay")
QMessageBox.warning(self, "Error", self.tr("Something went wrong, when installing overlay"))
return
self.info_stack.setCurrentIndex(0)
self.installed_version_lbl.setText(self.overlay.version)
self.installed_path_lbl.setText(self.overlay.install_path)
self.update_button.setVisible(False)
self.update_info_lbl.setVisible(False)
self.enable_gb.setEnabled(True)
def update_select_combo(self, i: None):
if i is None:
i = self.select_pfx_combo.currentIndex()
prefix = os.path.expanduser(self.select_pfx_combo.itemText(i))
if platform.system() != "Windows" and not os.path.exists(prefix):
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.enabled_cb.setChecked(overlay_enabled)
def change_enable(self):
enabled = self.enabled_cb.isChecked()
if not enabled:
eos.remove_registry_entries(self.current_prefix)
logger.info("Disabled Epic Overlay")
self.enabled_info_label.setText(self.tr("Disabled"))
else:
if not self.overlay:
available_installs = self.core.search_overlay_installs(self.current_prefix)
if not available_installs:
logger.error('No EOS overlay installs found!')
return
path = available_installs[0]
else:
path = self.overlay.install_path
if not self.core.is_overlay_install(path):
logger.error(f'Not a valid Overlay installation: {path}')
self.select_pfx_combo.removeItem(self.select_pfx_combo.currentIndex())
return
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}"')
eos.remove_registry_entries(self.current_prefix)
eos.add_registry_entries(path, self.current_prefix)
self.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.enabled_cb.setChecked(enabled)
def install_overlay(self, update=False):
if platform.system() != "Windows":
if QMessageBox.No == QMessageBox.question(self, "Warning",
self.tr("Epic overlay is currently not supported by wine, so it won't work. Install anyway? "),
QMessageBox.Yes | QMessageBox.No, QMessageBox.No):
return
base_path = os.path.expanduser("~/legendary/.overlay")
if update:
if not self.overlay:
self.info_stack.setCurrentIndex(1)
self.enable_gb.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="", base_path=base_path,
platform="Windows", overlay=True)
shared.signals.install_game.emit(options)
def uninstall_overlay(self):
if not self.core.is_overlay_installed():
logger.error('No legendary-managed overlay installation found.')
self.info_stack.setCurrentIndex(1)
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.select_pfx_combo.itemText(i) for i in range(self.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.info_stack.setCurrentIndex(1)
self.enable_gb.setDisabled(True)

View file

@ -6,6 +6,7 @@ from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool
from PyQt5.QtWidgets import QSizePolicy, QWidget, QFileDialog, QMessageBox from PyQt5.QtWidgets import QSizePolicy, QWidget, QFileDialog, QMessageBox
import rare.shared as shared import rare.shared as shared
from rare.components.tabs.settings.eos import EosWidget
from rare.components.tabs.settings.ubisoft_activation import UbiActivationHelper from rare.components.tabs.settings.ubisoft_activation import UbiActivationHelper
from rare.ui.components.tabs.settings.legendary import Ui_LegendarySettings from rare.ui.components.tabs.settings.legendary import Ui_LegendarySettings
from rare.utils.extra_widgets import PathEdit, IndicatorLineEdit from rare.utils.extra_widgets import PathEdit, IndicatorLineEdit
@ -84,6 +85,9 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
self.locale_layout.addWidget(self.locale_edit) self.locale_layout.addWidget(self.locale_edit)
self.ubi_helper = UbiActivationHelper(self.ubisoft_gb) self.ubi_helper = UbiActivationHelper(self.ubisoft_gb)
self.eos_widget = EosWidget()
self.layout().replaceWidget(self.eos_placeholder, self.eos_widget)
self.eos_placeholder.deleteLater()
self.refresh_game_meta_btn.clicked.connect(self.refresh_game_meta) self.refresh_game_meta_btn.clicked.connect(self.refresh_game_meta)
@ -96,14 +100,18 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
QThreadPool.globalInstance().start(worker) QThreadPool.globalInstance().start(worker)
@staticmethod @staticmethod
def locale_edit_cb(text: str) -> Tuple[bool, str]: def locale_edit_cb(text: str) -> Tuple[bool, str, str]:
if text: if text:
if re.match("^[a-zA-Z]{2,3}[-_][a-zA-Z]{2,3}$", text): if re.match("^[a-zA-Z]{2,3}[-_][a-zA-Z]{2,3}$", text):
language, country = text.replace("_", "-").split("-") language, country = text.replace("_", "-").split("-")
text = "-".join([language.lower(), country.upper()]) text = "-".join([language.lower(), country.upper()])
return bool(re.match("^[a-z]{2,3}-[A-Z]{2,3}$", text)), text if bool(re.match("^[a-z]{2,3}-[A-Z]{2,3}$", text)):
return True, text, ""
else: else:
return True, text return False, text, IndicatorLineEdit.reasons.wrong_format
else:
return True, text, ""
def locale_save_cb(self, text: str): def locale_save_cb(self, text: str):
if text: if text:

View file

@ -1,3 +1,4 @@
import os
from logging import getLogger from logging import getLogger
from PyQt5.QtWidgets import QFileDialog, QWidget from PyQt5.QtWidgets import QFileDialog, QWidget
@ -21,6 +22,7 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
self.wine_prefix = PathEdit( self.wine_prefix = PathEdit(
self.load_prefix(), self.load_prefix(),
file_type=QFileDialog.DirectoryOnly, file_type=QFileDialog.DirectoryOnly,
edit_func=lambda path: (os.path.isdir(path), path, PathEdit.reasons.dir_not_exist),
save_func=self.save_prefix, save_func=self.save_prefix,
) )
self.prefix_layout.addWidget(self.wine_prefix) self.prefix_layout.addWidget(self.wine_prefix)
@ -50,6 +52,7 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
def save_prefix(self, text: str): def save_prefix(self, text: str):
self.save_setting(text, f"{self.name}.env", "WINEPREFIX") self.save_setting(text, f"{self.name}.env", "WINEPREFIX")
self.save_setting(text, self.name, "wine_prefix") self.save_setting(text, self.name, "wine_prefix")
shared.signals.wine_prefix_updated.emit()
@staticmethod @staticmethod
def load_setting(section: str, setting: str, fallback: str = str()): def load_setting(section: str, setting: str, fallback: str = str()):

@ -1 +1 @@
Subproject commit d371c0b3c48a422ff7e6c966af649a1502300c2d Subproject commit 747afa1ea2e94ddd8fad80cbf8e407499929b68d

View file

@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/eos_widget.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# 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_EosWidget(object):
def setupUi(self, EosWidget):
EosWidget.setObjectName("EosWidget")
EosWidget.resize(1128, 319)
EosWidget.setWindowTitle("GroupBox")
self.horizontalLayout = QtWidgets.QHBoxLayout(EosWidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.info_stack = QtWidgets.QStackedWidget(EosWidget)
self.info_stack.setObjectName("info_stack")
self.horizontalLayout.addWidget(self.info_stack)
self.enable_gb = QtWidgets.QGroupBox(EosWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.enable_gb.sizePolicy().hasHeightForWidth())
self.enable_gb.setSizePolicy(sizePolicy)
self.enable_gb.setObjectName("enable_gb")
self.verticalLayout = QtWidgets.QVBoxLayout(self.enable_gb)
self.verticalLayout.setObjectName("verticalLayout")
self.select_pfx_combo = QtWidgets.QComboBox(self.enable_gb)
self.select_pfx_combo.setObjectName("select_pfx_combo")
self.verticalLayout.addWidget(self.select_pfx_combo, 0, QtCore.Qt.AlignTop)
self.enabled_cb = QtWidgets.QCheckBox(self.enable_gb)
self.enabled_cb.setObjectName("enabled_cb")
self.verticalLayout.addWidget(self.enabled_cb)
self.enabled_info_label = QtWidgets.QLabel(self.enable_gb)
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.verticalLayout.addWidget(self.enabled_info_label)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.horizontalLayout.addWidget(self.enable_gb)
self.installed_info_gb = QtWidgets.QGroupBox(EosWidget)
self.installed_info_gb.setObjectName("installed_info_gb")
self.formLayout = QtWidgets.QFormLayout(self.installed_info_gb)
self.formLayout.setObjectName("formLayout")
self.installed_version_info_lbl = QtWidgets.QLabel(self.installed_info_gb)
self.installed_version_info_lbl.setObjectName("installed_version_info_lbl")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.installed_version_info_lbl)
self.installed_version_lbl = QtWidgets.QLabel(self.installed_info_gb)
self.installed_version_lbl.setText("TextLabel")
self.installed_version_lbl.setObjectName("installed_version_lbl")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.installed_version_lbl)
self.installed_path_info_lbl = QtWidgets.QLabel(self.installed_info_gb)
self.installed_path_info_lbl.setObjectName("installed_path_info_lbl")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.installed_path_info_lbl)
self.installed_path_lbl = QtWidgets.QLabel(self.installed_info_gb)
self.installed_path_lbl.setText("TextLabel")
self.installed_path_lbl.setObjectName("installed_path_lbl")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.installed_path_lbl)
self.update_available_info_label = QtWidgets.QLabel(self.installed_info_gb)
self.update_available_info_label.setObjectName("update_available_info_label")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.update_available_info_label)
self.update_check_button = QtWidgets.QPushButton(self.installed_info_gb)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.update_check_button.sizePolicy().hasHeightForWidth())
self.update_check_button.setSizePolicy(sizePolicy)
self.update_check_button.setMaximumSize(QtCore.QSize(150, 16777215))
self.update_check_button.setObjectName("update_check_button")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.update_check_button)
self.uninstall_info_label = QtWidgets.QLabel(self.installed_info_gb)
self.uninstall_info_label.setObjectName("uninstall_info_label")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uninstall_info_label)
self.uninstall_button = QtWidgets.QPushButton(self.installed_info_gb)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uninstall_button.sizePolicy().hasHeightForWidth())
self.uninstall_button.setSizePolicy(sizePolicy)
self.uninstall_button.setMaximumSize(QtCore.QSize(150, 16777215))
self.uninstall_button.setObjectName("uninstall_button")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uninstall_button)
self.update_button = QtWidgets.QPushButton(self.installed_info_gb)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.update_button.sizePolicy().hasHeightForWidth())
self.update_button.setSizePolicy(sizePolicy)
self.update_button.setMaximumSize(QtCore.QSize(150, 16777215))
self.update_button.setObjectName("update_button")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.update_button)
self.update_info_lbl = QtWidgets.QLabel(self.installed_info_gb)
self.update_info_lbl.setObjectName("update_info_lbl")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.update_info_lbl)
self.horizontalLayout.addWidget(self.installed_info_gb)
self.install_overlay_gb = QtWidgets.QGroupBox(EosWidget)
self.install_overlay_gb.setObjectName("install_overlay_gb")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.install_overlay_gb)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.label = QtWidgets.QLabel(self.install_overlay_gb)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
self.label.setObjectName("label")
self.verticalLayout_4.addWidget(self.label)
self.install_button = QtWidgets.QPushButton(self.install_overlay_gb)
self.install_button.setObjectName("install_button")
self.verticalLayout_4.addWidget(self.install_button)
self.horizontalLayout.addWidget(self.install_overlay_gb)
self.retranslateUi(EosWidget)
self.info_stack.setCurrentIndex(-1)
QtCore.QMetaObject.connectSlotsByName(EosWidget)
def retranslateUi(self, EosWidget):
_translate = QtCore.QCoreApplication.translate
EosWidget.setTitle(_translate("EosWidget", "Epic Overlay settings"))
self.enable_gb.setTitle(_translate("EosWidget", "Enable / Disable"))
self.enabled_cb.setText(_translate("EosWidget", "Activated"))
self.installed_info_gb.setTitle(_translate("EosWidget", "Installed Info"))
self.installed_version_info_lbl.setText(_translate("EosWidget", "Installed version:"))
self.installed_path_info_lbl.setText(_translate("EosWidget", "Installed path"))
self.update_available_info_label.setText(_translate("EosWidget", "Updates"))
self.update_check_button.setText(_translate("EosWidget", "Check for Update"))
self.uninstall_info_label.setText(_translate("EosWidget", "Uninstall"))
self.uninstall_button.setText(_translate("EosWidget", "Uninstall"))
self.update_button.setText(_translate("EosWidget", "Update"))
self.update_info_lbl.setText(_translate("EosWidget", "Install Update"))
self.install_overlay_gb.setTitle(_translate("EosWidget", "Install Overlay"))
self.label.setText(_translate("EosWidget", "No overlays are installed"))
self.install_button.setText(_translate("EosWidget", "Install"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
EosWidget = QtWidgets.QGroupBox()
ui = Ui_EosWidget()
ui.setupUi(EosWidget)
EosWidget.show()
sys.exit(app.exec_())

View file

@ -0,0 +1,225 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EosWidget</class>
<widget class="QGroupBox" name="EosWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1128</width>
<height>319</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">GroupBox</string>
</property>
<property name="title">
<string>Epic Overlay settings</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QStackedWidget" name="info_stack">
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="enable_gb">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Enable / Disable</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignTop">
<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>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="installed_info_gb">
<property name="title">
<string>Installed Info</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="installed_version_info_lbl">
<property name="text">
<string>Installed version:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="installed_version_lbl">
<property name="text">
<string notr="true">TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="installed_path_info_lbl">
<property name="text">
<string>Installed path</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="installed_path_lbl">
<property name="text">
<string notr="true">TextLabel</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="update_available_info_label">
<property name="text">
<string>Updates</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="update_check_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Check for Update</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uninstall_info_label">
<property name="text">
<string>Uninstall</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="uninstall_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Uninstall</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="update_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Update</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="update_info_lbl">
<property name="text">
<string>Install Update</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="install_overlay_gb">
<property name="title">
<string>Install Overlay</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>No overlays are installed</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="install_button">
<property name="text">
<string>Install</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_LegendarySettings(object): class Ui_LegendarySettings(object):
def setupUi(self, LegendarySettings): def setupUi(self, LegendarySettings):
LegendarySettings.setObjectName("LegendarySettings") LegendarySettings.setObjectName("LegendarySettings")
LegendarySettings.resize(564, 374) LegendarySettings.resize(905, 568)
LegendarySettings.setWindowTitle("LegendarySettings") LegendarySettings.setWindowTitle("LegendarySettings")
self.gridLayout = QtWidgets.QGridLayout(LegendarySettings) self.gridLayout = QtWidgets.QGridLayout(LegendarySettings)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
@ -26,11 +26,13 @@ class Ui_LegendarySettings(object):
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.locale_group.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.locale_group.sizePolicy().hasHeightForWidth())
self.locale_group.setSizePolicy(sizePolicy) self.locale_group.setSizePolicy(sizePolicy)
self.locale_group.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.locale_group.setObjectName("locale_group") self.locale_group.setObjectName("locale_group")
self.locale_layout = QtWidgets.QVBoxLayout(self.locale_group) self.locale_layout = QtWidgets.QVBoxLayout(self.locale_group)
self.locale_layout.setObjectName("locale_layout") self.locale_layout.setObjectName("locale_layout")
self.right_layout.addWidget(self.locale_group) self.right_layout.addWidget(self.locale_group)
self.cleanup_group = QtWidgets.QGroupBox(LegendarySettings) self.cleanup_group = QtWidgets.QGroupBox(LegendarySettings)
self.cleanup_group.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.cleanup_group.setObjectName("cleanup_group") self.cleanup_group.setObjectName("cleanup_group")
self.cleanup_layout = QtWidgets.QVBoxLayout(self.cleanup_group) self.cleanup_layout = QtWidgets.QVBoxLayout(self.cleanup_group)
self.cleanup_layout.setObjectName("cleanup_layout") self.cleanup_layout.setObjectName("cleanup_layout")
@ -45,19 +47,16 @@ class Ui_LegendarySettings(object):
self.cleanup_layout.addWidget(self.refresh_game_meta_btn) self.cleanup_layout.addWidget(self.refresh_game_meta_btn)
self.right_layout.addWidget(self.cleanup_group) self.right_layout.addWidget(self.cleanup_group)
self.gridLayout.addLayout(self.right_layout, 0, 1, 1, 1) self.gridLayout.addLayout(self.right_layout, 0, 1, 1, 1)
self.ubisoft_gb = QtWidgets.QGroupBox(LegendarySettings)
self.ubisoft_gb.setObjectName("ubisoft_gb")
self.verticalLayout = QtWidgets.QVBoxLayout(self.ubisoft_gb)
self.verticalLayout.setObjectName("verticalLayout")
self.gridLayout.addWidget(self.ubisoft_gb, 1, 0, 1, 2)
self.left_layout = QtWidgets.QVBoxLayout() self.left_layout = QtWidgets.QVBoxLayout()
self.left_layout.setObjectName("left_layout") self.left_layout.setObjectName("left_layout")
self.install_dir_group = QtWidgets.QGroupBox(LegendarySettings) self.install_dir_group = QtWidgets.QGroupBox(LegendarySettings)
self.install_dir_group.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.install_dir_group.setObjectName("install_dir_group") self.install_dir_group.setObjectName("install_dir_group")
self.install_dir_layout = QtWidgets.QVBoxLayout(self.install_dir_group) self.install_dir_layout = QtWidgets.QVBoxLayout(self.install_dir_group)
self.install_dir_layout.setObjectName("install_dir_layout") self.install_dir_layout.setObjectName("install_dir_layout")
self.left_layout.addWidget(self.install_dir_group) self.left_layout.addWidget(self.install_dir_group)
self.download_group = QtWidgets.QGroupBox(LegendarySettings) self.download_group = QtWidgets.QGroupBox(LegendarySettings)
self.download_group.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.download_group.setObjectName("download_group") self.download_group.setObjectName("download_group")
self.download_layout = QtWidgets.QFormLayout(self.download_group) self.download_layout = QtWidgets.QFormLayout(self.download_group)
self.download_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.download_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
@ -129,8 +128,18 @@ class Ui_LegendarySettings(object):
self.download_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.disable_https_check) self.download_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.disable_https_check)
self.left_layout.addWidget(self.download_group) self.left_layout.addWidget(self.download_group)
self.gridLayout.addLayout(self.left_layout, 0, 0, 1, 1) self.gridLayout.addLayout(self.left_layout, 0, 0, 1, 1)
self.ubisoft_gb = QtWidgets.QGroupBox(LegendarySettings)
self.ubisoft_gb.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.ubisoft_gb.setObjectName("ubisoft_gb")
self.verticalLayout = QtWidgets.QVBoxLayout(self.ubisoft_gb)
self.verticalLayout.setObjectName("verticalLayout")
self.gridLayout.addWidget(self.ubisoft_gb, 2, 0, 1, 2)
self.eos_placeholder = QtWidgets.QGroupBox(LegendarySettings)
self.eos_placeholder.setTitle("GroupBox")
self.eos_placeholder.setObjectName("eos_placeholder")
self.gridLayout.addWidget(self.eos_placeholder, 3, 0, 1, 2)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 2, 0, 1, 2) self.gridLayout.addItem(spacerItem, 4, 0, 1, 2)
self.retranslateUi(LegendarySettings) self.retranslateUi(LegendarySettings)
QtCore.QMetaObject.connectSlotsByName(LegendarySettings) QtCore.QMetaObject.connectSlotsByName(LegendarySettings)
@ -142,7 +151,6 @@ class Ui_LegendarySettings(object):
self.clean_keep_manifests_button.setText(_translate("LegendarySettings", "Clean, but keep manifests")) self.clean_keep_manifests_button.setText(_translate("LegendarySettings", "Clean, but keep manifests"))
self.clean_button.setText(_translate("LegendarySettings", "Remove everything")) self.clean_button.setText(_translate("LegendarySettings", "Remove everything"))
self.refresh_game_meta_btn.setText(_translate("LegendarySettings", "Refresh game meta")) self.refresh_game_meta_btn.setText(_translate("LegendarySettings", "Refresh game meta"))
self.ubisoft_gb.setTitle(_translate("LegendarySettings", "Link Ubisoft Games"))
self.install_dir_group.setTitle(_translate("LegendarySettings", "Default Installation Directory")) self.install_dir_group.setTitle(_translate("LegendarySettings", "Default Installation Directory"))
self.download_group.setTitle(_translate("LegendarySettings", "Download Settings")) self.download_group.setTitle(_translate("LegendarySettings", "Download Settings"))
self.max_workers_label.setText(_translate("LegendarySettings", "Max Workers")) self.max_workers_label.setText(_translate("LegendarySettings", "Max Workers"))
@ -153,6 +161,7 @@ class Ui_LegendarySettings(object):
self.preferred_cdn_label.setText(_translate("LegendarySettings", "Preferred CDN")) self.preferred_cdn_label.setText(_translate("LegendarySettings", "Preferred CDN"))
self.preferred_cdn_line.setPlaceholderText(_translate("LegendarySettings", "Default")) self.preferred_cdn_line.setPlaceholderText(_translate("LegendarySettings", "Default"))
self.disable_https_label.setText(_translate("LegendarySettings", "Disable HTTPS")) self.disable_https_label.setText(_translate("LegendarySettings", "Disable HTTPS"))
self.ubisoft_gb.setTitle(_translate("LegendarySettings", "Link Ubisoft Games"))
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>564</width> <width>905</width>
<height>374</height> <height>568</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -27,6 +27,9 @@
<property name="title"> <property name="title">
<string>Locale</string> <string>Locale</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="locale_layout"/> <layout class="QVBoxLayout" name="locale_layout"/>
</widget> </widget>
</item> </item>
@ -35,6 +38,9 @@
<property name="title"> <property name="title">
<string>Cleanup</string> <string>Cleanup</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="cleanup_layout"> <layout class="QVBoxLayout" name="cleanup_layout">
<item> <item>
<widget class="QPushButton" name="clean_keep_manifests_button"> <widget class="QPushButton" name="clean_keep_manifests_button">
@ -62,14 +68,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="ubisoft_gb">
<property name="title">
<string>Link Ubisoft Games</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<layout class="QVBoxLayout" name="left_layout"> <layout class="QVBoxLayout" name="left_layout">
<item> <item>
@ -77,6 +75,9 @@
<property name="title"> <property name="title">
<string>Default Installation Directory</string> <string>Default Installation Directory</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="install_dir_layout"/> <layout class="QVBoxLayout" name="install_dir_layout"/>
</widget> </widget>
</item> </item>
@ -85,6 +86,9 @@
<property name="title"> <property name="title">
<string>Download Settings</string> <string>Download Settings</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QFormLayout" name="download_layout"> <layout class="QFormLayout" name="download_layout">
<property name="labelAlignment"> <property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
@ -219,6 +223,24 @@
</layout> </layout>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="ubisoft_gb">
<property name="title">
<string>Link Ubisoft Games</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QGroupBox" name="eos_placeholder">
<property name="title">
<string notr="true">GroupBox</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>

View file

@ -143,16 +143,25 @@ class FlowLayout(QLayout):
return parent.spacing() return parent.spacing()
class IndicatorReasons:
dir_not_empty = QCoreApplication.translate("IndicatorReasons", "Directory is not empty")
wrong_format = QCoreApplication.translate("IndicatorReasons", "Given text has wrong format")
game_not_installed = QCoreApplication.translate("IndicatorReasons", "Game is not installed or does not exist")
dir_not_exist = QCoreApplication.translate("IndicatorReasons", "Directory does not exist")
wrong_path = QCoreApplication.translate("IndicatorReasons", "Wrong Directory")
class IndicatorLineEdit(QWidget): class IndicatorLineEdit(QWidget):
textChanged = pyqtSignal(str) textChanged = pyqtSignal(str)
is_valid = False is_valid = False
reasons = IndicatorReasons()
def __init__( def __init__(
self, self,
text: str = "", text: str = "",
ph_text: str = "", ph_text: str = "",
completer: QCompleter = None, completer: QCompleter = None,
edit_func: Callable[[str], Tuple[bool, str]] = None, edit_func: Callable[[str], Tuple[bool, str, str]] = None,
save_func: Callable[[str], None] = None, save_func: Callable[[str], None] = None,
horiz_policy: QSizePolicy = QSizePolicy.Expanding, horiz_policy: QSizePolicy = QSizePolicy.Expanding,
parent=None, parent=None,
@ -219,20 +228,25 @@ class IndicatorLineEdit(QWidget):
self.hint_label.setFrameRect(self.line_edit.rect()) self.hint_label.setFrameRect(self.line_edit.rect())
self.hint_label.setText(text) self.hint_label.setText(text)
def __indicator(self, res): def __indicator(self, res, reason=None):
color = "green" if res else "red" color = "green" if res else "red"
self.indicator_label.setPixmap( self.indicator_label.setPixmap(
qta_icon("ei.info-circle", color=color).pixmap(16, 16) qta_icon("ei.info-circle", color=color).pixmap(16, 16)
) )
if reason:
self.indicator_label.setToolTip(reason)
else:
self.indicator_label.setToolTip("")
def __edit(self, text): def __edit(self, text):
if self.edit_func is not None: if self.edit_func is not None:
self.line_edit.blockSignals(True) self.line_edit.blockSignals(True)
self.is_valid, text = self.edit_func(text)
self.is_valid, text, reason = self.edit_func(text)
if text != self.line_edit.text(): if text != self.line_edit.text():
self.line_edit.setText(text) self.line_edit.setText(text)
self.line_edit.blockSignals(False) self.line_edit.blockSignals(False)
self.__indicator(self.is_valid) self.__indicator(self.is_valid, reason)
if self.is_valid: if self.is_valid:
self.__save(text) self.__save(text)
self.textChanged.emit(text) self.textChanged.emit(text)
@ -286,7 +300,7 @@ class PathEdit(IndicatorLineEdit):
type_filter: str = "", type_filter: str = "",
name_filter: str = "", name_filter: str = "",
ph_text: str = "", ph_text: str = "",
edit_func: Callable[[str], Tuple[bool, str]] = None, edit_func: Callable[[str], Tuple[bool, str, str]] = None,
save_func: Callable[[str], None] = None, save_func: Callable[[str], None] = None,
horiz_policy: QSizePolicy = QSizePolicy.Expanding, horiz_policy: QSizePolicy = QSizePolicy.Expanding,
parent=None, parent=None,

View file

@ -4,11 +4,11 @@ import shutil
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QObject, QRunnable from PyQt5.QtCore import pyqtSignal, QCoreApplication, QObject, QRunnable
from PyQt5.QtWidgets import QMessageBox
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
from legendary.models.game import VerifyResult from legendary.models.game import VerifyResult
from legendary.utils.lfs import validate_files from legendary.utils.lfs import validate_files
from rare import shared
logger = getLogger("Legendary Utils") logger = getLogger("Legendary Utils")
@ -92,7 +92,7 @@ def update_manifest(app_name: str, core: LegendaryCore):
class VerifySignals(QObject): class VerifySignals(QObject):
status = pyqtSignal(tuple) status = pyqtSignal(int, int, str)
summary = pyqtSignal(int, int, str) summary = pyqtSignal(int, int, str)
@ -100,52 +100,37 @@ class VerifyWorker(QRunnable):
num: int = 0 num: int = 0
total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized
def __init__(self, core, app_name): def __init__(self, app_name):
super(VerifyWorker, self).__init__() super(VerifyWorker, self).__init__()
self.core, self.app_name = core, app_name self.app_name = app_name
self.signals = VerifySignals() self.signals = VerifySignals()
self.setAutoDelete(True) self.setAutoDelete(True)
def run(self): def run(self):
if not self.core.is_installed(self.app_name): if not shared.core.is_installed(self.app_name):
logger.error(f'Game "{self.app_name}" is not installed') logger.error(f'Game "{self.app_name}" is not installed')
return return
igame = self.core.get_installed_game(self.app_name)
logger.info(f'Loading installed manifest for "{igame.title}"') logger.info(f'Loading installed manifest for "{self.app_name}"')
manifest_data, _ = self.core.get_installed_manifest(self.app_name) igame = shared.core.get_installed_game(self.app_name)
if not manifest_data: manifest_data, _ = shared.core.get_installed_manifest(self.app_name)
update_manifest(self.app_name, self.core) manifest = shared.core.load_manifest(manifest_data)
manifest_data, _ = self.core.get_installed_manifest(self.app_name)
if not manifest_data:
self.signals.summary.emit(-1, -1, self.app_name)
return
manifest = self.core.load_manifest(manifest_data) files = sorted(manifest.file_manifest_list.elements,
key=lambda a: a.filename.lower())
files = sorted(
manifest.file_manifest_list.elements, key=lambda a: a.filename.lower()
)
# build list of hashes # build list of hashes
file_list = [(f.filename, f.sha_hash.hex()) for f in files] file_list = [(f.filename, f.sha_hash.hex()) for f in files]
self.total = len(file_list) total = len(file_list)
self.num = 0 num = 0
failed = [] failed = []
missing = [] missing = []
_translate = QCoreApplication.translate logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
logger.info(
f'Verifying "{igame.title}" version "{manifest.meta.build_version}"'
)
repair_file = [] repair_file = []
try: for result, path, result_hash, _ in validate_files(igame.install_path, file_list):
for result, path, result_hash in validate_files( num += 1
igame.install_path, file_list self.signals.status.emit(num, total, self.app_name)
):
self.signals.status.emit((self.num, self.total, self.app_name))
self.num += 1
if result == VerifyResult.HASH_MATCH: if result == VerifyResult.HASH_MATCH:
repair_file.append(f"{result_hash}:{path}") repair_file.append(f"{result_hash}:{path}")
@ -158,38 +143,21 @@ class VerifyWorker(QRunnable):
logger.error(f'File is missing: "{path}"') logger.error(f'File is missing: "{path}"')
missing.append(path) missing.append(path)
else: else:
logger.error( logger.error(f'Other failure (see log), treating file as missing: "{path}"')
f'Other failure (see log), treating file as missing: "{path}"'
)
missing.append(path) missing.append(path)
except OSError as e:
QMessageBox.warning(
None, "Error", _translate("VerifyWorker", "Path does not exist")
)
logger.error(str(e))
except ValueError as e:
QMessageBox.warning(
None, "Error", _translate("VerifyWorker", "No files to validate")
)
logger.error(str(e))
# always write repair file, even if all match # always write repair file, even if all match
if repair_file: if repair_file:
repair_filename = os.path.join( repair_filename = os.path.join(shared.core.lgd.get_tmp_path(), f'{self.app_name}.repair')
self.core.lgd.get_tmp_path(), f"{self.app_name}.repair" with open(repair_filename, 'w') as f:
) f.write('\n'.join(repair_file))
with open(repair_filename, "w") as f:
f.write("\n".join(repair_file))
logger.debug(f'Written repair file to "{repair_filename}"') logger.debug(f'Written repair file to "{repair_filename}"')
if not missing and not failed: if not missing and not failed:
logger.info("Verification finished successfully.") logger.info('Verification finished successfully.')
self.signals.summary.emit(0, 0, self.app_name)
else: else:
logger.error( logger.warning(
f"Verification finished, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing." f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
)
self.signals.summary.emit(len(failed), len(missing), self.app_name) self.signals.summary.emit(len(failed), len(missing), self.app_name)

View file

@ -14,7 +14,7 @@ from legendary.models.game import Game, InstalledGame
@dataclass @dataclass
class InstallOptionsModel: class InstallOptionsModel:
app_name: str app_name: str
base_path: str = os.path.expanduser("~/legendary") base_path: str = ""
max_workers: int = os.cpu_count() * 2 max_workers: int = os.cpu_count() * 2
repair: bool = False repair: bool = False
no_install: bool = False no_install: bool = False
@ -24,6 +24,7 @@ class InstallOptionsModel:
update: bool = False update: bool = False
silent: bool = False silent: bool = False
platform: str = "" platform: str = ""
overlay: bool = False
def set_no_install(self, enabled: bool) -> None: def set_no_install(self, enabled: bool) -> None:
self.no_install = enabled self.no_install = enabled
@ -134,7 +135,11 @@ class Signals(QObject):
install_game = pyqtSignal(InstallOptionsModel) install_game = pyqtSignal(InstallOptionsModel)
installation_finished = pyqtSignal(bool, str) installation_finished = pyqtSignal(bool, str)
overlay_installation_finished = pyqtSignal()
update_gamelist = pyqtSignal(list) update_gamelist = pyqtSignal(list)
game_uninstalled = pyqtSignal(str) game_uninstalled = pyqtSignal(str)
set_discord_rpc = pyqtSignal(str) # app_name of running game set_discord_rpc = pyqtSignal(str) # app_name of running game
wine_prefix_updated = pyqtSignal()