1
0
Fork 0
mirror of synced 2024-06-29 03:31:06 +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.main_window import MainWindow
from rare.components.tray_icon import TrayIcon
from rare.utils import legendary_utils
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
@ -158,6 +159,16 @@ class App(QApplication):
self.mainwindow.show_window_centralized()
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.launch_dialog.close()
self.tray_icon = TrayIcon(self)

View file

@ -1,12 +1,15 @@
import os
import platform
from multiprocessing import Queue as MPQueue
from typing import Tuple
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 legendary.core import LegendaryCore
from legendary.models.downloading import ConditionCheckResult
from legendary.models.game import Game
from legendary.utils.selective_dl import games
from rare import shared
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
@ -19,7 +22,7 @@ class InstallDialog(QDialog, Ui_InstallDialog):
result_ready = pyqtSignal(InstallQueueItemModel)
def __init__(
self, dl_item: InstallQueueItemModel, update=False, silent=False, parent=None
self, dl_item: InstallQueueItemModel, update=False, silent=False, parent=None
):
super(InstallDialog, self).__init__(parent)
self.setupUi(self)
@ -30,7 +33,13 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.dl_item = dl_item
self.dl_item.status_q = MPQueue()
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.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.setWindowTitle(f'{self.windowTitle()} - {header} "{self.game.app_title}"')
default_path = os.path.expanduser("~/legendary")
if self.core.lgd.config.has_option("Legendary", "install_dir"):
default_path = self.core.lgd.config.get("Legendary", "install_dir")
if not self.dl_item.options.base_path:
self.dl_item.options.base_path = shared.core.lgd.config.get("Legendary", "install_dir",
fallback=os.path.expanduser("~/legendary"))
self.install_dir_edit = PathEdit(
path=default_path,
path=self.dl_item.options.base_path,
file_type=QFileDialog.DirectoryOnly,
edit_func=self.option_changed,
parent=self,
@ -82,8 +91,8 @@ class InstallDialog(QDialog, Ui_InstallDialog):
),
)
if (
self.platform_combo_box.currentText() == "Mac"
and platform.system() != "Darwin"
self.platform_combo_box.currentText() == "Mac"
and platform.system() != "Darwin"
)
else None
)
@ -125,6 +134,16 @@ class InstallDialog(QDialog, Ui_InstallDialog):
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.verify_button.clicked.connect(self.verify_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.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.force = self.force_download_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):
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.signals.result.connect(self.on_worker_result)
info_worker.signals.failed.connect(self.on_worker_failed)
@ -182,11 +202,16 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.get_options()
self.get_download_info()
def option_changed(self, path):
def option_changed(self, path) -> Tuple[bool, str, str]:
self.options_changed = True
self.install_button.setEnabled(False)
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):
if option == "download_only":
@ -250,6 +275,10 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.result_ready.emit(self.dl_item)
a0.accept()
def keyPressEvent(self, e: QKeyEvent) -> None:
if e.key() == Qt.Key_Escape:
self.cancel_clicked()
class InstallInfoWorkerSignals(QObject):
result = pyqtSignal(InstallDownloadModel)
@ -258,45 +287,70 @@ class InstallInfoWorkerSignals(QObject):
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__()
self.signals = InstallInfoWorkerSignals()
self.core = core
self.dl_item = dl_item
self.is_overlay_install = self.dl_item.options.overlay
self.game = game
@pyqtSlot()
def run(self):
try:
download = InstallDownloadModel(
*self.core.prepare_download(
app_name=self.dl_item.options.app_name,
base_path=self.dl_item.options.base_path,
force=self.dl_item.options.force,
no_install=self.dl_item.options.no_install,
status_q=self.dl_item.status_q,
# max_shm=,
max_workers=self.dl_item.options.max_workers,
# game_folder=,
# disable_patching=,
# override_manifest=,
# override_old_manifest=,
# override_base_url=,
platform=self.dl_item.options.platform,
# file_prefix_filter=,
# file_exclude_filter=,
# file_install_tag=,
# dl_optimizations=,
# dl_timeout=,
repair=self.dl_item.options.repair,
# repair_use_latest=,
ignore_space_req=self.dl_item.options.ignore_space_req,
# disable_delta=,
# override_delta_manifest=,
# reset_sdl=,
sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list,
if not self.is_overlay_install:
download = InstallDownloadModel(
*self.core.prepare_download(
app_name=self.dl_item.options.app_name,
base_path=self.dl_item.options.base_path,
force=self.dl_item.options.force,
no_install=self.dl_item.options.no_install,
status_q=self.dl_item.status_q,
# max_shm=,
max_workers=self.dl_item.options.max_workers,
# game_folder=,
# disable_patching=,
# override_manifest=,
# override_old_manifest=,
# override_base_url=,
platform=self.dl_item.options.platform,
# file_prefix_filter=,
# file_exclude_filter=,
# file_install_tag=,
# dl_optimizations=,
# dl_timeout=,
repair=self.dl_item.options.repair,
# repair_use_latest=,
ignore_space_req=self.dl_item.options.ignore_space_req,
# disable_delta=,
# override_delta_manifest=,
# reset_sdl=,
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)
else:
self.signals.failed.emit(

View file

@ -46,19 +46,19 @@ class BrowserLogin(QWidget, Ui_BrowserLogin):
return self.sid_edit.is_valid
@staticmethod
def text_changed(text) -> Tuple[bool, str]:
def text_changed(text) -> Tuple[bool, str, str]:
if text:
text = text.strip()
if text.startswith("{") and text.endswith("}"):
try:
text = json.loads(text).get("sid")
except json.JSONDecodeError:
return False, text
return False, text, IndicatorLineEdit.reasons.wrong_format
elif '"' in text:
text = text.strip('"')
return len(text) == 32, text
return len(text) == 32, text, IndicatorLineEdit.reasons.wrong_format
else:
return False, text
return False, text, ""
def do_login(self):
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.models.downloading import UIUpdate, WriterTask
from rare import shared
from rare.utils.models import InstallQueueItemModel
logger = getLogger("Download")
logger = getLogger("DownloadThread")
class DownloadThread(QThread):
@ -31,6 +32,7 @@ class DownloadThread(QThread):
self.igame = queue_item.download.igame
self.repair = queue_item.download.repair
self.repair_file = queue_item.download.repair_file
self.queue_item = queue_item
self._kill = False
def run(self):
@ -140,9 +142,15 @@ class DownloadThread(QThread):
return
self.status.emit("dl_finished")
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)
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:
postinstall = self.core.install_game(self.igame)
if postinstall:

View file

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

View file

@ -88,17 +88,17 @@ class GameInfo(QWidget, Ui_GameInfo):
)
return
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.summary.connect(self.finish_verify)
self.verify_progress.setValue(0)
self.verify_threads[self.game.app_name] = 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
if progress[2] == self.game.app_name:
self.verify_progress.setValue(progress[0] * 100 // progress[1])
if app_name == self.game.app_name:
self.verify_progress.setValue(num * 100 // total)
def finish_verify(self, failed, missing, app_name):
if failed == missing == 0:

View file

@ -66,7 +66,7 @@ class GameSettings(QWidget, Ui_GameSettings):
"",
file_type=QFileDialog.DirectoryOnly,
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,
)
self.cloud_gb.layout().addRow(
@ -296,12 +296,12 @@ class GameSettings(QWidget, Ui_GameSettings):
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:
text = os.path.expanduser("~/.proton")
return True, text
return True, 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):
self.core.lgd.config.set(

View file

@ -88,11 +88,11 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
self.egl_path_edit.setText(path)
@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:
return True, path
return True, path, ""
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:")
):
# path is a wine prefix
path = os.path.join(
@ -101,13 +101,13 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests",
)
elif not path.rstrip("/").endswith(
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests"
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests"
):
# 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):
return True, path
return False, path
return True, path, ""
return False, path, PathEdit.reasons.dir_not_exist
@staticmethod
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.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(os.path.join(path, ".egstore")):
return True, path
return True, path, ""
elif os.path.basename(path) in self.install_dir_list:
return True, path
return False, path
return True, path, ""
else:
return False, path, PathEdit.reasons.dir_not_exist
return False, path, ""
def path_changed(self, path):
self.info_label.setText(str())
@ -114,13 +116,13 @@ class ImportGroup(QGroupBox, Ui_ImportGroup):
else:
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:
return False, text
return False, text, ""
if text in self.app_name_list:
return True, text
return True, text, ""
else:
return False, text
return False, text, IndicatorLineEdit.reasons.game_not_installed
def app_name_changed(self, text):
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
import rare.shared as shared
from rare.components.tabs.settings.eos import EosWidget
from rare.components.tabs.settings.ubisoft_activation import UbiActivationHelper
from rare.ui.components.tabs.settings.legendary import Ui_LegendarySettings
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.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)
@ -96,14 +100,18 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
QThreadPool.globalInstance().start(worker)
@staticmethod
def locale_edit_cb(text: str) -> Tuple[bool, str]:
def locale_edit_cb(text: str) -> Tuple[bool, str, str]:
if text:
if re.match("^[a-zA-Z]{2,3}[-_][a-zA-Z]{2,3}$", text):
language, country = text.replace("_", "-").split("-")
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:
return False, text, IndicatorLineEdit.reasons.wrong_format
else:
return True, text
return True, text, ""
def locale_save_cb(self, text: str):
if text:

View file

@ -1,3 +1,4 @@
import os
from logging import getLogger
from PyQt5.QtWidgets import QFileDialog, QWidget
@ -21,6 +22,7 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
self.wine_prefix = PathEdit(
self.load_prefix(),
file_type=QFileDialog.DirectoryOnly,
edit_func=lambda path: (os.path.isdir(path), path, PathEdit.reasons.dir_not_exist),
save_func=self.save_prefix,
)
self.prefix_layout.addWidget(self.wine_prefix)
@ -50,6 +52,7 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
def save_prefix(self, text: str):
self.save_setting(text, f"{self.name}.env", "WINEPREFIX")
self.save_setting(text, self.name, "wine_prefix")
shared.signals.wine_prefix_updated.emit()
@staticmethod
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):
def setupUi(self, LegendarySettings):
LegendarySettings.setObjectName("LegendarySettings")
LegendarySettings.resize(564, 374)
LegendarySettings.resize(905, 568)
LegendarySettings.setWindowTitle("LegendarySettings")
self.gridLayout = QtWidgets.QGridLayout(LegendarySettings)
self.gridLayout.setObjectName("gridLayout")
@ -26,11 +26,13 @@ class Ui_LegendarySettings(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.locale_group.sizePolicy().hasHeightForWidth())
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_layout = QtWidgets.QVBoxLayout(self.locale_group)
self.locale_layout.setObjectName("locale_layout")
self.right_layout.addWidget(self.locale_group)
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_layout = QtWidgets.QVBoxLayout(self.cleanup_group)
self.cleanup_layout.setObjectName("cleanup_layout")
@ -45,19 +47,16 @@ class Ui_LegendarySettings(object):
self.cleanup_layout.addWidget(self.refresh_game_meta_btn)
self.right_layout.addWidget(self.cleanup_group)
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.setObjectName("left_layout")
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_layout = QtWidgets.QVBoxLayout(self.install_dir_group)
self.install_dir_layout.setObjectName("install_dir_layout")
self.left_layout.addWidget(self.install_dir_group)
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_layout = QtWidgets.QFormLayout(self.download_group)
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.left_layout.addWidget(self.download_group)
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)
self.gridLayout.addItem(spacerItem, 2, 0, 1, 2)
self.gridLayout.addItem(spacerItem, 4, 0, 1, 2)
self.retranslateUi(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_button.setText(_translate("LegendarySettings", "Remove everything"))
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.download_group.setTitle(_translate("LegendarySettings", "Download Settings"))
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_line.setPlaceholderText(_translate("LegendarySettings", "Default"))
self.disable_https_label.setText(_translate("LegendarySettings", "Disable HTTPS"))
self.ubisoft_gb.setTitle(_translate("LegendarySettings", "Link Ubisoft Games"))
if __name__ == "__main__":

View file

@ -4,10 +4,10 @@
<widget class="QWidget" name="LegendarySettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>564</width>
<height>374</height>
<x>0</x>
<y>0</y>
<width>905</width>
<height>568</height>
</rect>
</property>
<property name="windowTitle">
@ -17,84 +17,88 @@
<item row="0" column="1">
<layout class="QVBoxLayout" name="right_layout">
<item>
<widget class="QGroupBox" name="locale_group">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Locale</string>
</property>
<layout class="QVBoxLayout" name="locale_layout"/>
</widget>
<widget class="QGroupBox" name="locale_group">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Locale</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="locale_layout"/>
</widget>
</item>
<item>
<widget class="QGroupBox" name="cleanup_group">
<property name="title">
<string>Cleanup</string>
</property>
<layout class="QVBoxLayout" name="cleanup_layout">
<item>
<widget class="QPushButton" name="clean_keep_manifests_button">
<property name="text">
<string>Clean, but keep manifests</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clean_button">
<property name="text">
<string>Remove everything</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="refresh_game_meta_btn">
<property name="text">
<string>Refresh game meta</string>
</property>
</widget>
</item>
</layout>
<widget class="QGroupBox" name="cleanup_group">
<property name="title">
<string>Cleanup</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="cleanup_layout">
<item>
<widget class="QPushButton" name="clean_keep_manifests_button">
<property name="text">
<string>Clean, but keep manifests</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clean_button">
<property name="text">
<string>Remove everything</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="refresh_game_meta_btn">
<property name="text">
<string>Refresh game meta</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</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">
<layout class="QVBoxLayout" name="left_layout">
<item>
<widget class="QGroupBox" name="install_dir_group">
<property name="title">
<string>Default Installation Directory</string>
</property>
<layout class="QVBoxLayout" name="install_dir_layout"/>
</widget>
<widget class="QGroupBox" name="install_dir_group">
<property name="title">
<string>Default Installation Directory</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="install_dir_layout"/>
</widget>
</item>
<item>
<widget class="QGroupBox" name="download_group">
<property name="title">
<string>Download Settings</string>
</property>
<layout class="QFormLayout" name="download_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="max_workers_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
<widget class="QGroupBox" name="download_group">
<property name="title">
<string>Download Settings</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QFormLayout" name="download_layout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="max_workers_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
@ -209,28 +213,46 @@
<item row="3" column="1">
<widget class="QCheckBox" name="disable_https_check">
<property name="text">
<string/>
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</layout>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<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>
<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">
<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>
<resources/>

View file

@ -143,19 +143,28 @@ class FlowLayout(QLayout):
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):
textChanged = pyqtSignal(str)
is_valid = False
reasons = IndicatorReasons()
def __init__(
self,
text: str = "",
ph_text: str = "",
completer: QCompleter = None,
edit_func: Callable[[str], Tuple[bool, str]] = None,
save_func: Callable[[str], None] = None,
horiz_policy: QSizePolicy = QSizePolicy.Expanding,
parent=None,
self,
text: str = "",
ph_text: str = "",
completer: QCompleter = None,
edit_func: Callable[[str], Tuple[bool, str, str]] = None,
save_func: Callable[[str], None] = None,
horiz_policy: QSizePolicy = QSizePolicy.Expanding,
parent=None,
):
super(IndicatorLineEdit, self).__init__(parent=parent)
self.setObjectName("IndicatorLineEdit")
@ -219,20 +228,25 @@ class IndicatorLineEdit(QWidget):
self.hint_label.setFrameRect(self.line_edit.rect())
self.hint_label.setText(text)
def __indicator(self, res):
def __indicator(self, res, reason=None):
color = "green" if res else "red"
self.indicator_label.setPixmap(
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):
if self.edit_func is not None:
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():
self.line_edit.setText(text)
self.line_edit.blockSignals(False)
self.__indicator(self.is_valid)
self.__indicator(self.is_valid, reason)
if self.is_valid:
self.__save(text)
self.textChanged.emit(text)
@ -280,13 +294,13 @@ class PathEdit(IndicatorLineEdit):
compl_model = QFileSystemModel()
def __init__(
self,
self,
path: str = "",
file_type: QFileDialog.FileType = QFileDialog.AnyFile,
type_filter: str = "",
name_filter: 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,
horiz_policy: QSizePolicy = QSizePolicy.Expanding,
parent=None,

View file

@ -4,11 +4,11 @@ import shutil
from logging import getLogger
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QObject, QRunnable
from PyQt5.QtWidgets import QMessageBox
from legendary.core import LegendaryCore
from legendary.models.game import VerifyResult
from legendary.utils.lfs import validate_files
from rare import shared
logger = getLogger("Legendary Utils")
@ -92,7 +92,7 @@ def update_manifest(app_name: str, core: LegendaryCore):
class VerifySignals(QObject):
status = pyqtSignal(tuple)
status = pyqtSignal(int, int, str)
summary = pyqtSignal(int, int, str)
@ -100,97 +100,65 @@ class VerifyWorker(QRunnable):
num: int = 0
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__()
self.core, self.app_name = core, app_name
self.app_name = app_name
self.signals = VerifySignals()
self.setAutoDelete(True)
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')
return
igame = self.core.get_installed_game(self.app_name)
logger.info(f'Loading installed manifest for "{igame.title}"')
manifest_data, _ = self.core.get_installed_manifest(self.app_name)
if not manifest_data:
update_manifest(self.app_name, self.core)
manifest_data, _ = self.core.get_installed_manifest(self.app_name)
if not manifest_data:
self.signals.summary.emit(-1, -1, self.app_name)
return
logger.info(f'Loading installed manifest for "{self.app_name}"')
igame = shared.core.get_installed_game(self.app_name)
manifest_data, _ = shared.core.get_installed_manifest(self.app_name)
manifest = shared.core.load_manifest(manifest_data)
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
file_list = [(f.filename, f.sha_hash.hex()) for f in files]
self.total = len(file_list)
self.num = 0
total = len(file_list)
num = 0
failed = []
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 = []
try:
for result, path, result_hash in validate_files(
igame.install_path, file_list
):
self.signals.status.emit((self.num, self.total, self.app_name))
self.num += 1
for result, path, result_hash, _ in validate_files(igame.install_path, file_list):
num += 1
self.signals.status.emit(num, total, self.app_name)
if result == VerifyResult.HASH_MATCH:
repair_file.append(f"{result_hash}:{path}")
continue
elif result == VerifyResult.HASH_MISMATCH:
logger.error(f'File does not match hash: "{path}"')
repair_file.append(f"{result_hash}:{path}")
failed.append(path)
elif result == VerifyResult.FILE_MISSING:
logger.error(f'File is missing: "{path}"')
missing.append(path)
else:
logger.error(
f'Other failure (see log), treating file as missing: "{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))
if result == VerifyResult.HASH_MATCH:
repair_file.append(f"{result_hash}:{path}")
continue
elif result == VerifyResult.HASH_MISMATCH:
logger.error(f'File does not match hash: "{path}"')
repair_file.append(f"{result_hash}:{path}")
failed.append(path)
elif result == VerifyResult.FILE_MISSING:
logger.error(f'File is missing: "{path}"')
missing.append(path)
else:
logger.error(f'Other failure (see log), treating file as missing: "{path}"')
missing.append(path)
# always write repair file, even if all match
if repair_file:
repair_filename = os.path.join(
self.core.lgd.get_tmp_path(), f"{self.app_name}.repair"
)
with open(repair_filename, "w") as f:
f.write("\n".join(repair_file))
repair_filename = os.path.join(shared.core.lgd.get_tmp_path(), f'{self.app_name}.repair')
with open(repair_filename, 'w') as f:
f.write('\n'.join(repair_file))
logger.debug(f'Written repair file to "{repair_filename}"')
if not missing and not failed:
logger.info("Verification finished successfully.")
self.signals.summary.emit(0, 0, self.app_name)
logger.info('Verification finished successfully.')
else:
logger.error(
f"Verification finished, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing."
)
self.signals.summary.emit(len(failed), len(missing), self.app_name)
logger.warning(
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)
def import_game(core: LegendaryCore, app_name: str, path: str) -> str:

View file

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