diff --git a/rare/__init__.py b/rare/__init__.py index 2e00ef69..533cac3f 100644 --- a/rare/__init__.py +++ b/rare/__init__.py @@ -1,3 +1,3 @@ -__version__ = "1.8.7" +__version__ = "1.8.8" code_name = "Stellula Kakopo" diff --git a/rare/app.py b/rare/app.py index 8fbb639f..13491017 100644 --- a/rare/app.py +++ b/rare/app.py @@ -7,8 +7,9 @@ import sys import time import traceback from argparse import Namespace +from datetime import datetime -from PyQt5.QtCore import Qt, QThreadPool, QSettings, QTranslator +from PyQt5.QtCore import Qt, QThreadPool, QSettings, QTranslator, QTimer from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox from requests import HTTPError @@ -159,6 +160,19 @@ class App(QApplication): self.launch_dialog.login() + dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]) + dt_now = datetime.utcnow() + td = abs(dt_exp - dt_now) + self.timer = QTimer() + self.timer.timeout.connect(self.re_login) + self.timer.start(int(td.total_seconds() - 60)) + + def re_login(self): + logger.info("Session expires shortly. Renew session") + self.core.login() + self.timer.stop() + self.timer.deleteLater() + def show_mainwindow(self): if self.window_launched: self.mainwindow.show() diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index d616cdca..33560c6c 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -2,7 +2,7 @@ import os import platform from logging import getLogger -from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool +from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool, QSettings from PyQt5.QtWidgets import QDialog, QApplication from legendary.core import LegendaryCore from requests.exceptions import ConnectionError, HTTPError @@ -40,9 +40,10 @@ class ApiRequestWorker(QRunnable): self.signals = LaunchDialogSignals() self.setAutoDelete(True) self.core = LegendaryCoreSingleton() + self.settings = QSettings() def run(self) -> None: - if platform.system() == "Darwin" or "Mac" in self.core.get_installed_platforms(): + if self.settings.value("mac_meta", platform.system() == "Darwin", bool): try: result = self.core.get_game_and_dlc_list(True, "Mac") except HTTPError: @@ -50,9 +51,13 @@ class ApiRequestWorker(QRunnable): self.signals.result.emit(result, "mac") else: self.signals.result.emit(([], {}), "mac") - try: - result = self.core.get_game_and_dlc_list(True, "Win32") - except HTTPError: + + if self.settings.value("win32_meta", False, bool): + try: + result = self.core.get_game_and_dlc_list(True, "Win32") + except HTTPError: + result = [], {} + else: result = [], {} self.signals.result.emit(result, "32bit") diff --git a/rare/components/dialogs/login/browser_login.py b/rare/components/dialogs/login/browser_login.py index 900c0421..de726d68 100644 --- a/rare/components/dialogs/login/browser_login.py +++ b/rare/components/dialogs/login/browser_login.py @@ -27,7 +27,7 @@ class BrowserLogin(QWidget, Ui_BrowserLogin): self.core = core self.sid_edit = IndicatorLineEdit( - ph_text=self.tr("Insert SID here"), edit_func=self.text_changed, parent=self + placeholder=self.tr("Insert SID here"), edit_func=self.text_changed, parent=self ) self.link_text.setText(self.login_url) self.copy_button.setIcon(icon("mdi.content-copy", "fa.copy")) diff --git a/rare/components/extra/console.py b/rare/components/extra/console.py index 0e99c3bc..70551efb 100644 --- a/rare/components/extra/console.py +++ b/rare/components/extra/console.py @@ -43,8 +43,8 @@ class ConsoleWindow(QWidget): def log(self, text: str, end: str = "\n"): self.console.log(text + end) - def error(self, text): - self.console.error(text) + def error(self, text, end: str = "\n"): + self.console.error(text + end) class Console(QPlainTextEdit): @@ -55,11 +55,13 @@ class Console(QPlainTextEdit): self._cursor_output = self.textCursor() def log(self, text): - self._cursor_output.insertText(text) + html = f"

{text}

" + self._cursor_output.insertHtml(html) self.scroll_to_last_line() def error(self, text): - self._cursor_output.insertHtml(f'{text}') + html = f"

{text}

" + self._cursor_output.insertHtml(html) self.scroll_to_last_line() def scroll_to_last_line(self): diff --git a/rare/components/main_window.py b/rare/components/main_window.py index c017a1e7..2b8ec3d0 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -1,5 +1,4 @@ import os -import time from logging import getLogger from PyQt5.QtCore import Qt, QSettings, QTimer, QSize diff --git a/rare/components/tabs/account/__init__.py b/rare/components/tabs/account/__init__.py index 54f52ad1..8b327b73 100644 --- a/rare/components/tabs/account/__init__.py +++ b/rare/components/tabs/account/__init__.py @@ -3,6 +3,7 @@ import webbrowser from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMessageBox, QLabel, QPushButton from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton +from rare.utils.utils import icon class MiniWidget(QWidget): @@ -18,7 +19,7 @@ class MiniWidget(QWidget): self.layout.addWidget(QLabel(self.tr("Logged in as {}").format(username))) - self.open_browser = QPushButton(self.tr("Account settings")) + self.open_browser = QPushButton(icon("fa.external-link"), self.tr("Account settings")) self.open_browser.clicked.connect( lambda: webbrowser.open( "https://www.epicgames.com/account/personal?productName=epicgames" diff --git a/rare/components/tabs/games/game_info/__init__.py b/rare/components/tabs/games/game_info/__init__.py index df48e2b3..ec3b1c82 100644 --- a/rare/components/tabs/games/game_info/__init__.py +++ b/rare/components/tabs/games/game_info/__init__.py @@ -18,7 +18,7 @@ class GameInfoTabs(SideTabWidget): self.info = GameInfo(self, game_utils) self.addTab(self.info, self.tr("Information")) - self.settings = GameSettings(self.core, self) + self.settings = GameSettings(self) self.addTab(self.settings, self.tr("Settings")) self.dlc_list = dlcs @@ -30,12 +30,12 @@ class GameInfoTabs(SideTabWidget): def update_game(self, app_name: str): self.setCurrentIndex(1) self.info.update_game(app_name) - self.settings.update_game(app_name) + self.settings.load_settings(app_name) # DLC Tab: Disable if no dlcs available if ( - len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, [])) - == 0 + len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, [])) + == 0 ): self.setTabEnabled(3, False) else: diff --git a/rare/components/tabs/games/game_info/game_settings.py b/rare/components/tabs/games/game_info/game_settings.py index 16d17de9..d3b0be90 100644 --- a/rare/components/tabs/games/game_info/game_settings.py +++ b/rare/components/tabs/games/game_info/game_settings.py @@ -1,75 +1,29 @@ import os import platform from logging import getLogger -from pathlib import Path -from typing import Tuple -from PyQt5.QtCore import QSettings, QThreadPool, Qt -from PyQt5.QtWidgets import ( - QWidget, - QFileDialog, - QMessageBox, - QLabel, - QPushButton, - QSizePolicy -) -from legendary.core import LegendaryCore -from legendary.models.game import InstalledGame, Game +from PyQt5.QtCore import Qt, QThreadPool +from PyQt5.QtWidgets import QSizePolicy, QPushButton, QLabel, QFileDialog, QMessageBox +from legendary.models.game import Game, InstalledGame -from rare.components.tabs.settings.linux import LinuxSettings -from rare.components.tabs.settings.widgets.wrapper import WrapperSettings -from rare.ui.components.tabs.games.game_info.game_settings import Ui_GameSettings -from rare.components.tabs.settings.widgets.env_vars import EnvVars +from rare.components.tabs.settings import DefaultGameSettings +from rare.components.tabs.settings.widgets.pre_launch import PreLaunchSettings from rare.utils import config_helper from rare.utils.extra_widgets import PathEdit -from rare.utils.utils import WineResolver, get_raw_save_path -from rare.utils.utils import icon +from rare.utils.utils import icon, WineResolver, get_raw_save_path logger = getLogger("GameSettings") -def find_proton_wrappers(): - possible_proton_wrappers = [] - compatibilitytools_dirs = [ - os.path.expanduser("~/.steam/steam/steamapps/common"), - "/usr/share/steam/compatibilitytools.d", - os.path.expanduser("~/.steam/compatibilitytools.d"), - os.path.expanduser("~/.steam/root/compatibilitytools.d"), - ] - for c in compatibilitytools_dirs: - if os.path.exists(c): - for i in os.listdir(c): - proton = os.path.join(c, i, "proton") - compatibilitytool = os.path.join(c, i, "compatibilitytool.vdf") - toolmanifest = os.path.join(c, i, "toolmanifest.vdf") - if os.path.exists(proton) and ( - os.path.exists(compatibilitytool) or os.path.exists(toolmanifest) - ): - wrapper = f'"{proton}" run' - possible_proton_wrappers.append(wrapper) - if not possible_proton_wrappers: - logger.warning("Unable to find any Proton version") - return possible_proton_wrappers - - -class GameSettings(QWidget, Ui_GameSettings): +class GameSettings(DefaultGameSettings): game: Game igame: InstalledGame - # variable to no update when changing game - change = False - - def __init__(self, core: LegendaryCore, parent): - super(GameSettings, self).__init__(parent=parent) - self.setupUi(self) - - self.core = core - self.settings = QSettings() - - self.wrapper_settings = WrapperSettings() - + def __init__(self, parent=None): + super(GameSettings, self).__init__(False, parent) + self.pre_launch_settings = PreLaunchSettings() self.launch_settings_group.layout().addRow( - QLabel("Wrapper"), self.wrapper_settings + QLabel(self.tr("Pre launch command")), self.pre_launch_settings ) self.cloud_save_path_edit = PathEdit( @@ -103,49 +57,15 @@ class GameSettings(QWidget, Ui_GameSettings): f"{self.game.app_name}/auto_sync_cloud", self.cloud_sync.isChecked() ) ) + self.override_exe_edit.textChanged.connect( + lambda text: self.save_line_edit("override_exe", text) + ) self.launch_params.textChanged.connect( lambda x: self.save_line_edit("start_params", x) ) - if platform.system() != "Windows": - self.possible_proton_wrappers = find_proton_wrappers() - - self.proton_wrapper.addItems(self.possible_proton_wrappers) - self.proton_wrapper.currentIndexChanged.connect(self.change_proton) - - self.proton_prefix = PathEdit( - file_type=QFileDialog.DirectoryOnly, - edit_func=self.proton_prefix_edit, - save_func=self.proton_prefix_save, - placeholder=self.tr("Please select path for proton prefix") - ) - self.proton_prefix_layout.addWidget(self.proton_prefix) - - self.linux_settings = LinuxAppSettings() - # FIXME: Remove the spacerItem and margins from the linux settings - # FIXME: This should be handled differently at soem point in the future - self.linux_settings.layout().setContentsMargins(0, 0, 0, 0) - for item in [ - self.linux_settings.layout().itemAt(idx) - for idx in range(self.linux_settings.layout().count()) - ]: - if item.spacerItem(): - self.linux_settings.layout().removeItem(item) - del item - # FIXME: End of FIXME - self.linux_settings_layout.addWidget(self.linux_settings) - self.linux_settings_layout.setAlignment(Qt.AlignTop) - else: - self.linux_settings_widget.setVisible(False) self.game_settings_layout.setAlignment(Qt.AlignTop) - self.linux_settings.mangohud.set_wrapper_activated.connect( - lambda active: self.wrapper_settings.add_wrapper("mangohud") - if active else self.wrapper_settings.delete_wrapper("mangohud")) - - self.env_vars = EnvVars(self) - self.game_settings_contents_layout.addWidget(self.env_vars) - def compute_save_path(self): if ( self.core.is_installed(self.game.app_name) @@ -155,12 +75,17 @@ class GameSettings(QWidget, Ui_GameSettings): new_path = self.core.get_save_path(self.game.app_name) except Exception as e: logger.warning(str(e)) + resolver = WineResolver( + get_raw_save_path(self.game), self.game.app_name + ) + if not resolver.wine_env.get("WINEPREFIX"): + self.cloud_save_path_edit.setText("") + QMessageBox.warning(self, "Warning", "No wine prefix selected. Please set it in settings") + return self.cloud_save_path_edit.setText(self.tr("Loading")) self.cloud_save_path_edit.setDisabled(True) self.compute_save_path_button.setDisabled(True) - resolver = WineResolver( - get_raw_save_path(self.game), self.game.app_name, self.core - ) + app_name = self.game.app_name[:] resolver.signals.result_ready.connect( lambda x: self.wine_resolver_finished(x, app_name) @@ -228,61 +153,9 @@ class GameSettings(QWidget, Ui_GameSettings): config_helper.remove_option(self.game.app_name, option) config_helper.save_config() - def change_proton(self, i): - if self.change: - # First combo box entry: Don't use Proton - if i == 0: - self.wrapper_settings.delete_wrapper("proton") - config_helper.remove_option(self.game.app_name, "no_wine") - config_helper.remove_option(f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH") - config_helper.remove_option(f"{self.game.app_name}.env", "STEAM_COMPAT_CLIENT_INSTALL_PATH") - - self.proton_prefix.setEnabled(False) - # lk: TODO: This has to be fixed properly. - # lk: It happens because of the widget update. Mask it for now behind disabling the save button - - self.linux_settings.wine_groupbox.setEnabled(True) - else: - self.proton_prefix.setEnabled(True) - self.linux_settings.wine_groupbox.setEnabled(False) - wrapper = self.possible_proton_wrappers[i - 1] - - self.wrapper_settings.add_wrapper(wrapper) - config_helper.add_option(self.game.app_name, "no_wine", "true") - config_helper.add_option( - f"{self.game.app_name}.env", - "STEAM_COMPAT_DATA_PATH", - os.path.expanduser("~/.proton"), - ) - config_helper.add_option( - f"{self.game.app_name}.env", - "STEAM_COMPAT_CLIENT_INSTALL_PATH", - str(Path.home().joinpath(".steam", "steam")) - ) - - self.proton_prefix.setText(os.path.expanduser("~/.proton")) - - # Don't use Wine - self.linux_settings.wine_exec.setText("") - self.linux_settings.wine_prefix.setText("") - - config_helper.save_config() - - def proton_prefix_edit(self, text: str) -> Tuple[bool, str, str]: - if not text: - text = os.path.expanduser("~/.proton") - return True, text, "" - parent = os.path.dirname(text) - return os.path.exists(parent), text, PathEdit.reasons.dir_not_exist - - def proton_prefix_save(self, text: str): - config_helper.add_option( - f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH", text - ) - config_helper.save_config() - - def update_game(self, app_name: str): + def load_settings(self, app_name): self.change = False + super(GameSettings, self).load_settings(app_name) self.game = self.core.get_game(app_name) self.igame = self.core.get_installed_game(self.game.app_name) if self.igame: @@ -314,34 +187,12 @@ class GameSettings(QWidget, Ui_GameSettings): self.skip_update.setCurrentIndex(0) self.title.setTitle(self.game.app_title) - self.wrapper_settings.load_settings(app_name) if platform.system() != "Windows": - self.linux_settings.update_game(app_name) - if self.igame and self.igame.platform == "Mac": self.linux_settings_widget.setVisible(False) else: self.linux_settings_widget.setVisible(True) - proton = self.wrapper_settings.wrappers.get("proton", None) - if proton: - proton = proton.text.replace('"', "") - self.proton_prefix.setEnabled(True) - self.proton_wrapper.setCurrentText( - f'"{proton.replace(" run", "")}" run' - ) - proton_prefix = self.core.lgd.config.get( - f"{app_name}.env", - "STEAM_COMPAT_DATA_PATH", - fallback=Path.home().joinpath(".proton"), - ) - self.proton_prefix.setText(proton_prefix) - self.linux_settings.wine_groupbox.setEnabled(False) - else: - self.proton_wrapper.setCurrentIndex(0) - self.proton_prefix.setEnabled(False) - self.linux_settings.wine_groupbox.setEnabled(True) - if not self.game.supports_cloud_saves: self.cloud_group.setEnabled(False) self.cloud_save_path_edit.setText("") @@ -362,21 +213,7 @@ class GameSettings(QWidget, Ui_GameSettings): self.override_exe_edit.setText( self.core.lgd.config.get(self.game.app_name, "override_exe", fallback="") ) + + self.pre_launch_settings.load_settings(app_name) + self.change = True - self.env_vars.update_game(app_name) - - - - -class LinuxAppSettings(LinuxSettings): - def __init__(self): - super(LinuxAppSettings, self).__init__() - - def update_game(self, app_name): - self.name = app_name - self.wine_prefix.setText(self.load_prefix()) - self.wine_exec.setText(self.load_setting(self.name, "wine_executable")) - - self.dxvk.load_settings(self.name) - - self.mangohud.load_settings(self.name) diff --git a/rare/components/tabs/games/game_utils.py b/rare/components/tabs/games/game_utils.py index 0d6b88b1..fd8e1a4a 100644 --- a/rare/components/tabs/games/game_utils.py +++ b/rare/components/tabs/games/game_utils.py @@ -2,18 +2,18 @@ import datetime import os import platform import shutil -import webbrowser from dataclasses import dataclass from logging import getLogger -from PyQt5.QtCore import QObject, QSettings, QProcess, QProcessEnvironment, pyqtSignal +from PyQt5.QtCore import QObject, QSettings, QProcess, QProcessEnvironment, pyqtSignal, QUrl +from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMessageBox, QPushButton +from legendary.models.game import LaunchParameters, InstalledGame -from legendary.models.game import LaunchParameters -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton from rare.components.dialogs.uninstall_dialog import UninstallDialog from rare.components.extra.console import ConsoleWindow from rare.components.tabs.games import CloudSaveUtils +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton from rare.utils import legendary_utils from rare.utils.meta import RareGameMeta @@ -97,6 +97,7 @@ class GameUtils(QObject): except ValueError: logger.info("Cancel startup") self.sync_finished(app_name) + return except AssertionError: dont_sync_after_finish = True else: @@ -167,162 +168,32 @@ class GameUtils(QObject): ) return - process = GameProcess(app_name) - process.setProcessChannelMode(GameProcess.MergedChannels) + def _launch_real(): + process = self._get_process(app_name, env) + self.console.log("\n"*2) + if game.third_party_store != "Origin": + self._launch_game(igame, process, offline, skip_update_check, ask_always_sync) + else: + self._launch_origin(app_name, process) - if game.third_party_store != "Origin": - if not offline: - if not skip_update_check and not self.core.is_noupdate_game(app_name): - # check updates - try: - latest = self.core.get_asset( - app_name, igame.platform, update=False - ) - except ValueError: - self.finished.emit(app_name, self.tr("Metadata doesn't exist")) - return - else: - if latest.build_version != igame.version: - self.finished.emit(app_name, self.tr("Please update game")) - return - - params: LaunchParameters = self.core.get_launch_parameters( - app_name=app_name, offline=offline, wine_bin=wine_bin, wine_pfx=wine_pfx - ) - - full_params = list() - - if os.environ.get("container") == "flatpak": - full_params.extend(["flatpak-spawn", "--host"]) - - full_params.extend(params.launch_command) - full_params.append( - os.path.join(params.game_directory, params.game_executable) - ) - full_params.extend(params.game_parameters) - full_params.extend(params.egl_parameters) - full_params.extend(params.user_parameters) - - process.setWorkingDirectory(params.working_directory) - environment = QProcessEnvironment() - full_env = os.environ.copy() - full_env.update(params.environment) - for env, value in full_env.items(): - environment.insert(env, value) - - if platform.system() != "Windows": - # wine prefixes - for env in ["STEAM_COMPAT_DATA_PATH", "WINEPREFIX"]: - if val := full_env.get(env): - if not os.path.exists(val): - try: - os.makedirs(val) - except PermissionError as e: - logger.error(str(e)) - QMessageBox.warning( - None, - "Error", - self.tr( - "Error while launching {}. No permission to create {} for {}" - ).format(game.app_title, val, env), - ) - process.deleteLater() - return - # check wine executable - if shutil.which(full_params[0]) is None: - # wine binary does not exist - QMessageBox.warning( - None, - "Warning", - self.tr( - "'{}' does not exist. Please change it in Settings" - ).format(full_params[0]), - ) - process.deleteLater() - return - - if shutil.which(full_params[0]) is None: - QMessageBox.warning(None, "Warning", self.tr("'{}' does not exist").format(full_params[0])) + env = self.core.get_app_environment(app_name, wine_pfx=wine_pfx) + pre_cmd, wait = self.core.get_pre_launch_command(app_name) + if pre_cmd: + pre_cmd = pre_cmd.split() + pre_proc = self._launch_pre_command(env) + self.console.log("\n"*2) + pre_proc.start(pre_cmd[0], pre_cmd[1:]) + if wait: + pre_proc.finished.connect(_launch_real) return - - process.setProcessEnvironment(environment) - process.game_finished.connect(self.game_finished) - running_game = RunningGameModel( - process=process, app_name=app_name, always_ask_sync=ask_always_sync - ) - - process.start(full_params[0], full_params[1:]) - self.game_launched.emit(app_name) - self.signals.set_discord_rpc.emit(app_name) - logger.info(f"{game.app_title} launched") - - self.running_games[game.app_name] = running_game - - else: - origin_uri = self.core.get_origin_uri(game.app_name, self.args.offline) - logger.info("Launch Origin Game: ") - if platform.system() == "Windows": - webbrowser.open(origin_uri) - self.finished.emit(app_name, "") - return - wine_pfx = self.core.lgd.config.get( - game.app_name, "wine_prefix", fallback=os.path.expanduser("~/.wine") - ) - if not wine_bin: - wine_bin = self.core.lgd.config.get( - game.app_name, "wine_executable", fallback="/usr/bin/wine" - ) - - if shutil.which(wine_bin) is None: - # wine binary does not exist - QMessageBox.warning( - None, - "Warning", - self.tr( - "Wine executable '{}' does not exist. Please change it in Settings" - ).format(wine_bin), - ) - process.deleteLater() - return - - env = self.core.get_app_environment(game.app_name, wine_pfx=wine_pfx) - - if not env.get("WINEPREFIX") and not os.path.exists("/usr/bin/wine"): - logger.error( - f"In order to launch Origin correctly you must specify the wine binary and prefix " - f"to use in the configuration file or command line. See the README for details." - ) - self.finished.emit( - app_name, - self.tr("No wine executable selected. Please set it in settings"), - ) - return - - environment = QProcessEnvironment() - for e in env: - environment.insert(e, env[e]) - process.setProcessEnvironment(environment) - process.finished.connect(lambda x: self.game_finished(x, game.app_name)) - process.start(wine_bin, [origin_uri]) - - if QSettings().value("show_console", False, bool): - self.console.show() - process.readyReadStandardOutput.connect( - lambda: self.console.log( - str(process.readAllStandardOutput().data(), "utf-8", "ignore") - ) - ) - process.readyReadStandardError.connect( - lambda: self.console.error( - str(process.readAllStandardError().data(), "utf-8", "ignore") - ) - ) + _launch_real() def game_finished(self, exit_code, app_name): logger.info(f"Game exited with exit code: {exit_code}") + self.console.log(f"Game exited with code: {exit_code}") self.signals.set_discord_rpc.emit("") is_origin = self.core.get_game(app_name).third_party_store == "Origin" - if exit_code == 53 and is_origin: + if exit_code == 1 and is_origin: msg_box = QMessageBox() msg_box.setText( self.tr( @@ -334,7 +205,8 @@ class GameUtils(QObject): resp = msg_box.exec() # click install button if resp == 0: - webbrowser.open("https://www.dm.origin.com/download") + QDesktopServices.openUrl(QUrl("https://www.dm.origin.com/download")) + return if exit_code != 0: QMessageBox.warning( None, @@ -351,16 +223,14 @@ class GameUtils(QObject): self.running_games.pop(app_name) self.finished.emit(app_name, "") - if QSettings().value("show_console", False, bool): - self.console.log(f"Game exited with code: {exit_code}") - if self.core.get_game(app_name).supports_cloud_saves: if exit_code != 0: r = QMessageBox.question( None, "Question", self.tr( - "Game exited with code {}, which is not a normal code. It could be caused by a crash. Do you want to sync cloud saves" + "Game exited with code {}, which is not a normal code. " + "It could be caused by a crash. Do you want to sync cloud saves" ).format(exit_code), buttons=QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.Yes, @@ -369,6 +239,145 @@ class GameUtils(QObject): return self.cloud_save_utils.game_finished(app_name, game.always_ask_sync) + def _launch_pre_command(self, env: dict): + proc = QProcess() + environment = QProcessEnvironment() + for e in env: + environment.insert(e, env[e]) + proc.setProcessEnvironment(environment) + + proc.readyReadStandardOutput.connect( + lambda: self.console.log( + str(proc.readAllStandardOutput().data(), "utf-8", "ignore") + ) + ) + proc.readyReadStandardError.connect( + lambda: self.console.error( + str(proc.readAllStandardError().data(), "utf-8", "ignore") + ) + ) + return proc + + def _get_process(self, app_name, env): + process = GameProcess(app_name) + + environment = QProcessEnvironment() + for e in env: + environment.insert(e, env[e]) + process.setProcessEnvironment(environment) + + process.readyReadStandardOutput.connect( + lambda: self.console.log( + str(process.readAllStandardOutput().data(), "utf-8", "ignore") + ) + ) + process.readyReadStandardError.connect( + lambda: self.console.error( + str(process.readAllStandardError().data(), "utf-8", "ignore") + ) + ) + process.finished.connect(lambda x: self.game_finished(x, app_name)) + process.stateChanged.connect( + lambda state: self.console.show() + if (state == QProcess.Running + and QSettings().value("show_console", False, bool)) + else None + ) + return process + + def _launch_origin(self, app_name, process: QProcess): + origin_uri = self.core.get_origin_uri(app_name, self.args.offline) + logger.info("Launch Origin Game: ") + if platform.system() == "Windows": + QDesktopServices.openUrl(QUrl(origin_uri)) + self.finished.emit(app_name, "") + return + + command = self.core.get_app_launch_command(app_name) + + if not os.path.exists(command[0]) and shutil.which(command[0]) is None: + # wine binary does not exist + QMessageBox.warning( + None, "Warning", + self.tr( + "'{}' does not exist. Please change it in Settings" + ).format(command[0]), + ) + process.deleteLater() + return + command.append(origin_uri) + process.start(command[0], command[1:]) + + def _launch_game(self, igame: InstalledGame, process: QProcess, offline: bool, + skip_update_check: bool, ask_always_sync: bool): + if not offline: # skip for update + if not skip_update_check and not self.core.is_noupdate_game(igame.app_name): + # check updates + try: + latest = self.core.get_asset( + igame.app_name, igame.platform, update=False + ) + except ValueError: + self.finished.emit(igame.app_name, self.tr("Metadata doesn't exist")) + return + else: + if latest.build_version != igame.version: + self.finished.emit(igame.app_name, self.tr("Please update game")) + return + + params: LaunchParameters = self.core.get_launch_parameters( + app_name=igame.app_name, offline=offline + ) + + full_params = list() + + if os.environ.get("container") == "flatpak": + full_params.extend(["flatpak-spawn", "--host"]) + + full_params.extend(params.launch_command) + full_params.append( + os.path.join(params.game_directory, params.game_executable) + ) + full_params.extend(params.game_parameters) + full_params.extend(params.egl_parameters) + full_params.extend(params.user_parameters) + + process.setWorkingDirectory(params.working_directory) + + if platform.system() != "Windows": + # wine prefixes + for env in ["STEAM_COMPAT_DATA_PATH", "WINEPREFIX"]: + if val := process.processEnvironment().value(env, ""): + if not os.path.exists(val): + try: + os.makedirs(val) + except PermissionError as e: + logger.error(str(e)) + QMessageBox.warning( + None, + "Error", + self.tr( + "Error while launching {}. No permission to create {} for {}" + ).format(igame.title, val, env), + ) + process.deleteLater() + return + # check wine executable + + if shutil.which(full_params[0]) is None: + QMessageBox.warning(None, "Warning", self.tr("'{}' does not exist").format(full_params[0])) + return + running_game = RunningGameModel( + process=process, app_name=igame.app_name, always_ask_sync=ask_always_sync + ) + process.start(full_params[0], full_params[1:]) + + self.game_launched.emit(igame.app_name) + self.signals.set_discord_rpc.emit(igame.app_name) + logger.info(f"{igame.title} launched") + + self.running_games[igame.app_name] = running_game + def sync_finished(self, app_name): if app_name in self.launch_queue.keys(): self.cloud_save_finished.emit(app_name) diff --git a/rare/components/tabs/games/head_bar.py b/rare/components/tabs/games/head_bar.py index f0243633..ba104333 100644 --- a/rare/components/tabs/games/head_bar.py +++ b/rare/components/tabs/games/head_bar.py @@ -28,7 +28,7 @@ class GameListHeadBar(QWidget): self.filter = QComboBox() self.filter.addItems( [ - self.tr("All"), + self.tr("All games"), self.tr("Installed only"), self.tr("Offline Games"), ]) diff --git a/rare/components/tabs/games/import_sync/egl_sync_group.py b/rare/components/tabs/games/import_sync/egl_sync_group.py index c10a5ab0..eb096f5d 100644 --- a/rare/components/tabs/games/import_sync/egl_sync_group.py +++ b/rare/components/tabs/games/import_sync/egl_sync_group.py @@ -48,7 +48,7 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): if not self.core.egl.programdata_path: self.egl_path_info.setText(self.tr("Updating...")) wine_resolver = WineResolver( - PathSpec.egl_programdata, "default", self.core + PathSpec.egl_programdata, "default" ) wine_resolver.signals.result_ready.connect(self.wine_resolver_cb) self.thread_pool.start(wine_resolver) diff --git a/rare/components/tabs/games/import_sync/import_group.py b/rare/components/tabs/games/import_sync/import_group.py index 811a60ab..e296d6e7 100644 --- a/rare/components/tabs/games/import_sync/import_group.py +++ b/rare/components/tabs/games/import_sync/import_group.py @@ -87,7 +87,7 @@ class ImportGroup(QGroupBox, Ui_ImportGroup): self.path_edit_layout.addWidget(self.path_edit) self.app_name = IndicatorLineEdit( - ph_text=self.tr("Use in case the app name was not found automatically"), + placeholder=self.tr("Use in case the app name was not found automatically"), completer=AppNameCompleter( app_names=[ (i.app_name, i.app_title) for i in self.api_results.game_list diff --git a/rare/components/tabs/settings/__init__.py b/rare/components/tabs/settings/__init__.py index cab992c8..eeb1e101 100644 --- a/rare/components/tabs/settings/__init__.py +++ b/rare/components/tabs/settings/__init__.py @@ -3,22 +3,21 @@ import platform from rare.utils.extra_widgets import SideTabWidget from .about import About from .legendary import LegendarySettings -from .linux import LinuxSettings +from rare.components.tabs.settings.widgets.linux import LinuxSettings from .rare import RareSettings +from .default_game_settings import DefaultGameSettings class SettingsTab(SideTabWidget): def __init__(self, parent=None): super(SettingsTab, self).__init__(parent=parent) - about_tab = 2 + about_tab = 3 self.rare_settings = RareSettings() self.addTab(self.rare_settings, "Rare") self.addTab(LegendarySettings(), "Legendary") - if platform.system() != "Windows": - self.addTab(LinuxSettings(), "Linux") - about_tab = 3 + self.addTab(DefaultGameSettings(True, self), self.tr("Games"), self.tr("Default game settings")) self.about = About() self.addTab(self.about, "About", "About") diff --git a/rare/components/tabs/settings/default_game_settings.py b/rare/components/tabs/settings/default_game_settings.py new file mode 100644 index 00000000..3fbca800 --- /dev/null +++ b/rare/components/tabs/settings/default_game_settings.py @@ -0,0 +1,127 @@ +import platform +from logging import getLogger + +from PyQt5.QtCore import QSettings, Qt +from PyQt5.QtWidgets import ( + QWidget, + QLabel +) + +from rare.components.tabs.settings.widgets.env_vars import EnvVars +from rare.components.tabs.settings.widgets.linux import LinuxSettings +from rare.components.tabs.settings.widgets.proton import ProtonSettings +from rare.components.tabs.settings.widgets.wrapper import WrapperSettings +from rare.shared import LegendaryCoreSingleton +from rare.ui.components.tabs.games.game_info.game_settings import Ui_GameSettings +from rare.utils import config_helper + +logger = getLogger("GameSettings") + + +class DefaultGameSettings(QWidget, Ui_GameSettings): + # variable to no update when changing game + change = False + app_name: str + + def __init__(self, is_default, parent=None): + super(DefaultGameSettings, self).__init__(parent=parent) + self.setupUi(self) + self.core = LegendaryCoreSingleton() + self.settings = QSettings() + + self.wrapper_settings = WrapperSettings() + + self.launch_settings_group.layout().addRow( + QLabel("Wrapper"), self.wrapper_settings + ) + + if platform.system() != "Windows": + self.linux_settings = LinuxAppSettings() + self.proton_settings = ProtonSettings(self.linux_settings, self.wrapper_settings) + self.game_settings_layout.replaceWidget(self.proton_placeholder, self.proton_settings) + self.proton_placeholder.deleteLater() + + # FIXME: Remove the spacerItem and margins from the linux settings + # FIXME: This should be handled differently at soem point in the future + self.linux_settings.layout().setContentsMargins(0, 0, 0, 0) + for item in [ + self.linux_settings.layout().itemAt(idx) + for idx in range(self.linux_settings.layout().count()) + ]: + if item.spacerItem(): + self.linux_settings.layout().removeItem(item) + del item + # FIXME: End of FIXME + self.linux_settings_layout.addWidget(self.linux_settings) + self.linux_settings_layout.setAlignment(Qt.AlignTop) + + self.game_settings_layout.setAlignment(Qt.AlignTop) + + self.linux_settings.mangohud.set_wrapper_activated.connect( + lambda active: self.wrapper_settings.add_wrapper("mangohud") + if active else self.wrapper_settings.delete_wrapper("mangohud")) + + else: + self.linux_settings_widget.setVisible(False) + + self.env_vars = EnvVars(self) + self.game_settings_layout.addWidget(self.env_vars) + + if is_default: + for i in range(4): # remove some entries which are not supported by default + self.launch_settings_layout.removeRow(0) + + self.cloud_group.deleteLater() + self.load_settings("default") + + def save_line_edit(self, option, value): + if value: + config_helper.add_option(self.game.app_name, option, value) + else: + config_helper.remove_option(self.game.app_name, option) + config_helper.save_config() + + if option == "wine_prefix": + if self.game.supports_cloud_saves: + self.compute_save_path() + + def update_combobox(self, i, option): + if self.change: + # remove section + if i: + if i == 1: + config_helper.add_option(self.game.app_name, option, "true") + if i == 2: + config_helper.add_option(self.game.app_name, option, "false") + else: + config_helper.remove_option(self.game.app_name, option) + config_helper.save_config() + + def load_settings(self, app_name): + self.app_name = app_name + self.wrapper_settings.load_settings(app_name) + if platform.system() != "Windows": + self.linux_settings.update_game(app_name) + proton = self.wrapper_settings.wrappers.get("proton", "") + if proton: + proton = proton.text + self.proton_settings.load_settings(app_name, proton) + if proton: + self.linux_settings.wine_groupbox.setEnabled(False) + else: + self.linux_settings.wine_groupbox.setEnabled(True) + self.env_vars.update_game(app_name) + + +class LinuxAppSettings(LinuxSettings): + def __init__(self): + super(LinuxAppSettings, self).__init__() + + def update_game(self, app_name): + self.name = app_name + self.wine_prefix.setText(self.load_prefix()) + self.wine_exec.setText(self.load_setting(self.name, "wine_executable")) + + self.dxvk.load_settings(self.name) + + self.mangohud.load_settings(self.name) diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py index 37af5c13..1f7c3435 100644 --- a/rare/components/tabs/settings/legendary.py +++ b/rare/components/tabs/settings/legendary.py @@ -1,13 +1,14 @@ +import platform import re from logging import getLogger from typing import Tuple -from PyQt5.QtCore import Qt, QRunnable, QObject, pyqtSignal, QThreadPool +from PyQt5.QtCore import Qt, QRunnable, QObject, pyqtSignal, QThreadPool, QSettings from PyQt5.QtWidgets import QSizePolicy, QWidget, QFileDialog, QMessageBox -from rare.shared import LegendaryCoreSingleton from rare.components.tabs.settings.widgets.eos import EosWidget from rare.components.tabs.settings.widgets.ubisoft_activation import UbiActivationHelper +from rare.shared import LegendaryCoreSingleton from rare.ui.components.tabs.settings.legendary import Ui_LegendarySettings from rare.utils.extra_widgets import PathEdit, IndicatorLineEdit from rare.utils.utils import get_size @@ -38,6 +39,7 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): def __init__(self, parent=None): super(LegendarySettings, self).__init__(parent=parent) self.setupUi(self) + self.settings = QSettings() self.core = LegendaryCoreSingleton() @@ -89,6 +91,12 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): self.eos_widget = EosWidget() self.left_layout.insertWidget(3, self.eos_widget, alignment=Qt.AlignTop) + self.win32_cb.setChecked(self.settings.value("win32_meta", False, bool)) + self.win32_cb.stateChanged.connect(lambda: self.settings.setValue("win32_meta", self.win32_cb.isChecked())) + + self.mac_cb.setChecked(self.settings.value("mac_meta", platform.system() == "Darwin", bool)) + self.mac_cb.stateChanged.connect(lambda: self.settings.setValue("mac_meta", self.mac_cb.isChecked())) + self.refresh_game_meta_btn.clicked.connect(self.refresh_game_meta) def refresh_game_meta(self): diff --git a/rare/components/tabs/settings/linux.py b/rare/components/tabs/settings/widgets/linux.py similarity index 89% rename from rare/components/tabs/settings/linux.py rename to rare/components/tabs/settings/widgets/linux.py index dcf0003a..104fcf00 100644 --- a/rare/components/tabs/settings/linux.py +++ b/rare/components/tabs/settings/widgets/linux.py @@ -1,4 +1,5 @@ import os +import shutil from logging import getLogger from PyQt5.QtWidgets import QFileDialog, QWidget @@ -37,6 +38,7 @@ class LinuxSettings(QWidget, Ui_LinuxSettings): self.load_setting(self.name, "wine_executable"), file_type=QFileDialog.ExistingFile, name_filter="Wine executable (wine wine64)", + edit_func=lambda text: (os.path.exists(text) or not text, text, PathEdit.reasons.dir_not_exist), save_func=lambda text: self.save_setting( text, section=self.name, setting="wine_executable" ), @@ -46,9 +48,15 @@ class LinuxSettings(QWidget, Ui_LinuxSettings): # dxvk self.dxvk = DxvkSettings() self.overlay_layout.addWidget(self.dxvk) + self.dxvk.load_settings(self.name) self.mangohud = MangoHudSettings() self.overlay_layout.addWidget(self.mangohud) + self.mangohud.load_settings(self.name) + + if not shutil.which("mangohud"): + self.mangohud.setDisabled(True) + self.mangohud.setToolTip(self.tr("Mangohud is not installed or not in path")) def load_prefix(self) -> str: return self.load_setting( diff --git a/rare/components/tabs/settings/widgets/pre_launch.py b/rare/components/tabs/settings/widgets/pre_launch.py new file mode 100644 index 00000000..8b016be9 --- /dev/null +++ b/rare/components/tabs/settings/widgets/pre_launch.py @@ -0,0 +1,61 @@ +import os +import shutil +from typing import Tuple + +from PyQt5.QtWidgets import QHBoxLayout, QCheckBox, QFileDialog + +from rare.shared import LegendaryCoreSingleton +from rare.utils import config_helper +from rare.utils.extra_widgets import IndicatorLineEdit, PathEdit + + +class PreLaunchSettings(QHBoxLayout): + app_name: str + + def __init__(self): + super(PreLaunchSettings, self).__init__() + self.core = LegendaryCoreSingleton() + self.edit = PathEdit( + path="", + placeholder=self.tr("Path to script"), + file_type=QFileDialog.ExistingFile, + edit_func=self.edit_command, + save_func=self.save_pre_launch_command, + ) + self.layout().addWidget(self.edit) + + self.wait_check = QCheckBox(self.tr("Wait for finish")) + self.layout().addWidget(self.wait_check) + self.wait_check.stateChanged.connect(self.save_wait_finish) + + def edit_command(self, text: str) -> Tuple[bool, str, str]: + if not text.strip(): + return True, text, "" + + if not os.path.isfile(text.split()[0]) and not shutil.which(text.split()[0]): + return False, text, IndicatorLineEdit.reasons.file_not_exist + else: + return True, text, "" + + def save_pre_launch_command(self, text): + if text: + config_helper.add_option(self.app_name, "pre_launch_command", text) + self.wait_check.setDisabled(False) + else: + config_helper.remove_option(self.app_name, "pre_launch_command") + self.wait_check.setDisabled(True) + config_helper.remove_option(self.app_name, "pre_launch_wait") + + def save_wait_finish(self): + config_helper.add_option(self.app_name, "pre_launch_wait", str(self.wait_check.isChecked()).lower()) + + def load_settings(self, app_name): + self.app_name = app_name + + command = self.core.lgd.config.get(app_name, "pre_launch_command", fallback="") + self.edit.setText(command) + + wait = self.core.lgd.config.getboolean(app_name, "pre_launch_wait", fallback=False) + self.wait_check.setChecked(wait) + + self.wait_check.setEnabled(bool(command)) diff --git a/rare/components/tabs/settings/widgets/proton.py b/rare/components/tabs/settings/widgets/proton.py new file mode 100644 index 00000000..96145c5b --- /dev/null +++ b/rare/components/tabs/settings/widgets/proton.py @@ -0,0 +1,141 @@ +import os +from logging import getLogger +from pathlib import Path +from typing import Tuple + +from PyQt5.QtWidgets import QGroupBox, QFileDialog + +from rare.components.tabs.settings import LinuxSettings +from .wrapper import WrapperSettings +from rare.ui.components.tabs.settings.proton import Ui_ProtonSettings +from rare.utils import config_helper +from rare.utils.extra_widgets import PathEdit +from rare.shared import LegendaryCoreSingleton + +logger = getLogger("Proton") + +def find_proton_combos(): + possible_proton_combos = [] + compatibilitytools_dirs = [ + os.path.expanduser("~/.steam/steam/steamapps/common"), + "/usr/share/steam/compatibilitytools.d", + os.path.expanduser("~/.steam/compatibilitytools.d"), + os.path.expanduser("~/.steam/root/compatibilitytools.d"), + ] + for c in compatibilitytools_dirs: + if os.path.exists(c): + for i in os.listdir(c): + proton = os.path.join(c, i, "proton") + compatibilitytool = os.path.join(c, i, "compatibilitytool.vdf") + toolmanifest = os.path.join(c, i, "toolmanifest.vdf") + if os.path.exists(proton) and ( + os.path.exists(compatibilitytool) or os.path.exists(toolmanifest) + ): + wrapper = f'"{proton}" run' + possible_proton_combos.append(wrapper) + if not possible_proton_combos: + logger.warning("Unable to find any Proton version") + return possible_proton_combos + + +class ProtonSettings(QGroupBox, Ui_ProtonSettings): + + app_name: str + changeable = True + + def __init__(self, linux_settings: LinuxSettings, wrapper_settings: WrapperSettings): + super(ProtonSettings, self).__init__() + self.setupUi(self) + self._linux_settings = linux_settings + self._wrapper_settings = wrapper_settings + self.core = LegendaryCoreSingleton() + self.possible_proton_combos = find_proton_combos() + + self.proton_combo.addItems(self.possible_proton_combos) + self.proton_combo.currentIndexChanged.connect(self.change_proton) + + self.proton_prefix = PathEdit( + file_type=QFileDialog.DirectoryOnly, + edit_func=self.proton_prefix_edit, + save_func=self.proton_prefix_save, + placeholder=self.tr("Please select path for proton prefix") + ) + self.layout().replaceWidget(self.placeholder_prefix_edit, self.proton_prefix) + self.placeholder_prefix_edit.deleteLater() + + def change_proton(self, i): + if not self.changeable: + return + # First combo box entry: Don't use Proton + if i == 0: + self._wrapper_settings.delete_wrapper("proton") + config_helper.remove_option(self.app_name, "no_wine") + config_helper.remove_option(f"{self.app_name}.env", "STEAM_COMPAT_DATA_PATH") + config_helper.remove_option(f"{self.app_name}.env", "STEAM_COMPAT_CLIENT_INSTALL_PATH") + + self.proton_prefix.setEnabled(False) + # lk: TODO: This has to be fixed properly. + # lk: It happens because of the widget update. Mask it for now behind disabling the save button + + self._linux_settings.wine_groupbox.setEnabled(True) + else: + self.proton_prefix.setEnabled(True) + self._linux_settings.wine_groupbox.setEnabled(False) + wrapper = self.possible_proton_combos[i - 1] + self._wrapper_settings.add_wrapper(wrapper) + config_helper.add_option(self.app_name, "no_wine", "true") + config_helper.add_option( + f"{self.app_name}.env", + "STEAM_COMPAT_DATA_PATH", + os.path.expanduser("~/.proton"), + ) + config_helper.add_option( + f"{self.app_name}.env", + "STEAM_COMPAT_CLIENT_INSTALL_PATH", + str(Path.home().joinpath(".steam", "steam")) + ) + + self.proton_prefix.setText(os.path.expanduser("~/.proton")) + + # Don't use Wine + self._linux_settings.wine_exec.setText("") + self._linux_settings.wine_prefix.setText("") + + config_helper.save_config() + + def proton_prefix_edit(self, text: str) -> Tuple[bool, str, str]: + if not text: + text = os.path.expanduser("~/.proton") + return True, text, "" + parent_dir = os.path.dirname(text) + return os.path.exists(parent_dir), text, PathEdit.reasons.dir_not_exist + + def proton_prefix_save(self, text: str): + if not self.changeable: + return + config_helper.add_option( + f"{self.app_name}.env", "STEAM_COMPAT_DATA_PATH", text + ) + config_helper.save_config() + + def load_settings(self, app_name: str, proton: str): + self.changeable = False + self.app_name = app_name + proton = proton.replace('"', "") + self.proton_prefix.setEnabled(bool(proton)) + if proton: + print(proton) + self.proton_combo.setCurrentText( + f'"{proton.replace(" run", "")}" run' + ) + + else: + self.proton_combo.setCurrentIndex(0) + + proton_prefix = self.core.lgd.config.get( + f"{app_name}.env", + "STEAM_COMPAT_DATA_PATH", + fallback=str(Path.home().joinpath(".proton")), + ) + self.proton_prefix.setText(proton_prefix) + self.changeable = True diff --git a/rare/components/tabs/settings/widgets/wrapper.py b/rare/components/tabs/settings/widgets/wrapper.py index ab3e0e42..819f6753 100644 --- a/rare/components/tabs/settings/widgets/wrapper.py +++ b/rare/components/tabs/settings/widgets/wrapper.py @@ -166,6 +166,9 @@ class WrapperSettings(QWidget, Ui_WrapperSettings): self.widget_stack.setCurrentIndex(0) + if widget := self.wrappers.get(show_text, None): + widget.deleteLater() + widget = WrapperWidget(text, show_text, self.scroll_content) self.scroll_content.layout().addWidget(widget) self.adjust_scrollarea( @@ -173,7 +176,6 @@ class WrapperSettings(QWidget, Ui_WrapperSettings): self.wrapper_scroll.horizontalScrollBar().maximum() ) widget.delete_wrapper.connect(self.delete_wrapper) - self.wrappers[show_text] = widget if not from_load: diff --git a/rare/ui/components/tabs/games/game_info/game_settings.py b/rare/ui/components/tabs/games/game_info/game_settings.py index b3a8bcb8..8d15aa57 100644 --- a/rare/ui/components/tabs/games/game_info/game_settings.py +++ b/rare/ui/components/tabs/games/game_info/game_settings.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_GameSettings(object): def setupUi(self, GameSettings): GameSettings.setObjectName("GameSettings") - GameSettings.resize(545, 348) + GameSettings.resize(558, 357) self.game_settings_layout = QtWidgets.QVBoxLayout(GameSettings) self.game_settings_layout.setObjectName("game_settings_layout") self.launch_settings_group = QtWidgets.QGroupBox(GameSettings) @@ -82,30 +82,12 @@ class Ui_GameSettings(object): self.cloud_sync.setObjectName("cloud_sync") self.cloud_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cloud_sync) self.game_settings_layout.addWidget(self.cloud_group) - self.proton_group = QtWidgets.QGroupBox(GameSettings) - self.proton_group.setObjectName("proton_group") - self.proton_layout = QtWidgets.QFormLayout(self.proton_group) + self.proton_placeholder = QtWidgets.QWidget(GameSettings) + self.proton_placeholder.setObjectName("proton_placeholder") + self.proton_layout = QtWidgets.QFormLayout(self.proton_placeholder) self.proton_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.proton_layout.setObjectName("proton_layout") - self.proton_wrapper_label = QtWidgets.QLabel(self.proton_group) - self.proton_wrapper_label.setObjectName("proton_wrapper_label") - self.proton_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.proton_wrapper_label) - self.proton_wrapper = QtWidgets.QComboBox(self.proton_group) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.proton_wrapper.sizePolicy().hasHeightForWidth()) - self.proton_wrapper.setSizePolicy(sizePolicy) - self.proton_wrapper.setObjectName("proton_wrapper") - self.proton_wrapper.addItem("") - self.proton_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.proton_wrapper) - self.proton_prefix_label = QtWidgets.QLabel(self.proton_group) - self.proton_prefix_label.setObjectName("proton_prefix_label") - self.proton_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.proton_prefix_label) - self.proton_prefix_layout = QtWidgets.QVBoxLayout() - self.proton_prefix_layout.setObjectName("proton_prefix_layout") - self.proton_layout.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.proton_prefix_layout) - self.game_settings_layout.addWidget(self.proton_group) + self.game_settings_layout.addWidget(self.proton_placeholder) self.linux_settings_widget = QtWidgets.QWidget(GameSettings) self.linux_settings_widget.setObjectName("linux_settings_widget") self.linux_settings_layout = QtWidgets.QVBoxLayout(self.linux_settings_widget) @@ -134,10 +116,6 @@ class Ui_GameSettings(object): self.override_exe_edit.setPlaceholderText(_translate("GameSettings", "Relative path to launch executable")) self.cloud_group.setTitle(_translate("GameSettings", "Cloud Saves")) self.cloud_sync_label.setText(_translate("GameSettings", "Sync with cloud")) - self.proton_group.setTitle(_translate("GameSettings", "Proton Settings")) - self.proton_wrapper_label.setText(_translate("GameSettings", "Proton")) - self.proton_wrapper.setItemText(0, _translate("GameSettings", "Don\'t use Proton")) - self.proton_prefix_label.setText(_translate("GameSettings", "Prefix")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/games/game_info/game_settings.ui b/rare/ui/components/tabs/games/game_info/game_settings.ui index 6e114e79..9e28ab37 100644 --- a/rare/ui/components/tabs/games/game_info/game_settings.ui +++ b/rare/ui/components/tabs/games/game_info/game_settings.ui @@ -6,8 +6,8 @@ 0 0 - 545 - 348 + 558 + 357 @@ -157,46 +157,11 @@ - - - Proton Settings - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - Proton - - - - - - - - 0 - 0 - - - - - Don't use Proton - - - - - - - - Prefix - - - - - - diff --git a/rare/ui/components/tabs/settings/legendary.py b/rare/ui/components/tabs/settings/legendary.py index c5964855..f98146af 100644 --- a/rare/ui/components/tabs/settings/legendary.py +++ b/rare/ui/components/tabs/settings/legendary.py @@ -14,10 +14,52 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_LegendarySettings(object): def setupUi(self, LegendarySettings): LegendarySettings.setObjectName("LegendarySettings") - LegendarySettings.resize(552, 312) + LegendarySettings.resize(654, 498) LegendarySettings.setWindowTitle("LegendarySettings") - self.legendary_layout = QtWidgets.QHBoxLayout(LegendarySettings) - self.legendary_layout.setObjectName("legendary_layout") + self.gridLayout = QtWidgets.QGridLayout(LegendarySettings) + self.gridLayout.setObjectName("gridLayout") + self.right_layout = QtWidgets.QVBoxLayout() + self.right_layout.setObjectName("right_layout") + self.locale_group = QtWidgets.QGroupBox(LegendarySettings) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + 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, 0, QtCore.Qt.AlignTop) + 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") + self.clean_keep_manifests_button = QtWidgets.QPushButton(self.cleanup_group) + self.clean_keep_manifests_button.setObjectName("clean_keep_manifests_button") + self.cleanup_layout.addWidget(self.clean_keep_manifests_button) + self.clean_button = QtWidgets.QPushButton(self.cleanup_group) + self.clean_button.setObjectName("clean_button") + self.cleanup_layout.addWidget(self.clean_button) + self.right_layout.addWidget(self.cleanup_group, 0, QtCore.Qt.AlignTop) + self.meta_group = QtWidgets.QGroupBox(LegendarySettings) + self.meta_group.setObjectName("meta_group") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.meta_group) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.win32_cb = QtWidgets.QCheckBox(self.meta_group) + self.win32_cb.setObjectName("win32_cb") + self.verticalLayout_2.addWidget(self.win32_cb) + self.mac_cb = QtWidgets.QCheckBox(self.meta_group) + self.mac_cb.setObjectName("mac_cb") + self.verticalLayout_2.addWidget(self.mac_cb) + self.refresh_game_meta_btn = QtWidgets.QPushButton(self.meta_group) + self.refresh_game_meta_btn.setObjectName("refresh_game_meta_btn") + self.verticalLayout_2.addWidget(self.refresh_game_meta_btn) + self.right_layout.addWidget(self.meta_group) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.right_layout.addItem(spacerItem) + self.gridLayout.addLayout(self.right_layout, 0, 1, 1, 1) self.left_layout = QtWidgets.QVBoxLayout() self.left_layout.setObjectName("left_layout") self.install_dir_group = QtWidgets.QGroupBox(LegendarySettings) @@ -104,46 +146,23 @@ class Ui_LegendarySettings(object): self.verticalLayout = QtWidgets.QVBoxLayout(self.ubisoft_gb) self.verticalLayout.setObjectName("verticalLayout") self.left_layout.addWidget(self.ubisoft_gb, 0, QtCore.Qt.AlignTop) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.left_layout.addItem(spacerItem) - self.legendary_layout.addLayout(self.left_layout) - self.right_layout = QtWidgets.QVBoxLayout() - self.right_layout.setObjectName("right_layout") - self.locale_group = QtWidgets.QGroupBox(LegendarySettings) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - 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, 0, QtCore.Qt.AlignTop) - 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") - self.clean_keep_manifests_button = QtWidgets.QPushButton(self.cleanup_group) - self.clean_keep_manifests_button.setObjectName("clean_keep_manifests_button") - self.cleanup_layout.addWidget(self.clean_keep_manifests_button) - self.clean_button = QtWidgets.QPushButton(self.cleanup_group) - self.clean_button.setObjectName("clean_button") - self.cleanup_layout.addWidget(self.clean_button) - self.refresh_game_meta_btn = QtWidgets.QPushButton(self.cleanup_group) - self.refresh_game_meta_btn.setObjectName("refresh_game_meta_btn") - self.cleanup_layout.addWidget(self.refresh_game_meta_btn) - self.right_layout.addWidget(self.cleanup_group, 0, QtCore.Qt.AlignTop) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.right_layout.addItem(spacerItem1) - self.legendary_layout.addLayout(self.right_layout) + self.left_layout.addItem(spacerItem1) + self.gridLayout.addLayout(self.left_layout, 0, 0, 1, 1) self.retranslateUi(LegendarySettings) QtCore.QMetaObject.connectSlotsByName(LegendarySettings) def retranslateUi(self, LegendarySettings): _translate = QtCore.QCoreApplication.translate + self.locale_group.setTitle(_translate("LegendarySettings", "Locale")) + self.cleanup_group.setTitle(_translate("LegendarySettings", "Cleanup")) + self.clean_keep_manifests_button.setText(_translate("LegendarySettings", "Clean, but keep manifests")) + self.clean_button.setText(_translate("LegendarySettings", "Remove everything")) + self.meta_group.setTitle(_translate("LegendarySettings", "Game metadata")) + self.win32_cb.setText(_translate("LegendarySettings", "Load 32 bit data")) + self.mac_cb.setText(_translate("LegendarySettings", "Load MacOS data")) + self.refresh_game_meta_btn.setText(_translate("LegendarySettings", "Refresh game meta")) 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")) @@ -155,11 +174,6 @@ class Ui_LegendarySettings(object): 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")) - self.locale_group.setTitle(_translate("LegendarySettings", "Locale")) - self.cleanup_group.setTitle(_translate("LegendarySettings", "Cleanup")) - 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")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/settings/legendary.ui b/rare/ui/components/tabs/settings/legendary.ui index 4546e743..ee6dc545 100644 --- a/rare/ui/components/tabs/settings/legendary.ui +++ b/rare/ui/components/tabs/settings/legendary.ui @@ -1,266 +1,323 @@ - LegendarySettings - - - - 0 - 0 - 552 - 312 - - - - LegendarySettings - - - - - - - - Default Installation Directory - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - Download Settings - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + LegendarySettings + + + + 0 + 0 + 552 + 312 + - - - - - 0 - 0 - - - - Max Workers - - - - - - - - - - 0 - 0 - + + LegendarySettings + + + + + + Clean, but keep manifests + + + + + + + Remove everything + + + + + + + + + Game metadata - - 0 + + + + + Load 32 bit data + + + + + + + Load MacOS data + + + + + + + Refresh game meta + + + + + + + + + + Qt::Vertical - - 16 + + + 20 + 40 + - - 0 - - - - - - - - true - - - - Less is slower (0: Default) - - - - - - - - - Max Memory - - - - - - - - - - 0 - 0 - - - - MiB - - - 0 - - - 10240 - - - 128 - - - 1024 - - - - - - - - true - - - - Less is slower (0: Default) - - - - - - - - - Preferred CDN - - - - - - - Default - - - - - - - Disable HTTPS - - - - - - - - - - - - - - - - - Link Ubisoft Games - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - 0 - 0 - - - - Locale - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - Cleanup - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - Clean, but keep manifests - - - - - - - Remove everything - - - - - - - Refresh game meta - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - + + + + + + + + Default Installation Directory + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + Download Settings + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 0 + + + + Max Workers + + + + + + + + + + 0 + 0 + + + + 0 + + + 16 + + + 0 + + + + + + + + true + + + + Less is slower (0: Default) + + + + + + + + + Max Memory + + + + + + + + + + 0 + 0 + + + + MiB + + + 0 + + + 10240 + + + 128 + + + 1024 + + + + + + + + true + + + + Less is slower (0: Default) + + + + + + + + + Preferred CDN + + + + + + + Default + + + + + + + Disable HTTPS + + + + + + + + + + + + + + + + + Link Ubisoft Games + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + + + + Locale + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + Cleanup + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + Clean, but keep manifests + + + + + + + Remove everything + + + + + + + Refresh game meta + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + diff --git a/rare/ui/components/tabs/settings/proton.py b/rare/ui/components/tabs/settings/proton.py new file mode 100644 index 00000000..cb81927c --- /dev/null +++ b/rare/ui/components/tabs/settings/proton.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/proton.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_ProtonSettings(object): + def setupUi(self, ProtonSettings): + ProtonSettings.setObjectName("ProtonSettings") + ProtonSettings.resize(400, 300) + ProtonSettings.setWindowTitle("GroupBox") + self.formLayout = QtWidgets.QFormLayout(ProtonSettings) + self.formLayout.setObjectName("formLayout") + self.proton_wrapper_label = QtWidgets.QLabel(ProtonSettings) + self.proton_wrapper_label.setObjectName("proton_wrapper_label") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.proton_wrapper_label) + self.proton_combo = QtWidgets.QComboBox(ProtonSettings) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.proton_combo.sizePolicy().hasHeightForWidth()) + self.proton_combo.setSizePolicy(sizePolicy) + self.proton_combo.setObjectName("proton_combo") + self.proton_combo.addItem("") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.proton_combo) + self.proton_prefix_label = QtWidgets.QLabel(ProtonSettings) + self.proton_prefix_label.setObjectName("proton_prefix_label") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.proton_prefix_label) + self.placeholder_prefix_edit = QtWidgets.QLineEdit(ProtonSettings) + self.placeholder_prefix_edit.setObjectName("placeholder_prefix_edit") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.placeholder_prefix_edit) + + self.retranslateUi(ProtonSettings) + QtCore.QMetaObject.connectSlotsByName(ProtonSettings) + + def retranslateUi(self, ProtonSettings): + _translate = QtCore.QCoreApplication.translate + ProtonSettings.setTitle(_translate("ProtonSettings", "Proton Settings")) + self.proton_wrapper_label.setText(_translate("ProtonSettings", "Proton")) + self.proton_combo.setItemText(0, _translate("ProtonSettings", "Don\'t use Proton")) + self.proton_prefix_label.setText(_translate("ProtonSettings", "Prefix")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + ProtonSettings = QtWidgets.QGroupBox() + ui = Ui_ProtonSettings() + ui.setupUi(ProtonSettings) + ProtonSettings.show() + sys.exit(app.exec_()) diff --git a/rare/ui/components/tabs/settings/proton.ui b/rare/ui/components/tabs/settings/proton.ui new file mode 100644 index 00000000..1a627552 --- /dev/null +++ b/rare/ui/components/tabs/settings/proton.ui @@ -0,0 +1,56 @@ + + + ProtonSettings + + + + 0 + 0 + 400 + 300 + + + + GroupBox + + + Proton Settings + + + + + + Proton + + + + + + + + 0 + 0 + + + + + Don't use Proton + + + + + + + + Prefix + + + + + + + + + + + diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index dad4420a..d0611fef 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -150,6 +150,7 @@ class IndicatorReasons: 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") + file_not_exist = QCoreApplication.translate("IndicatorReasons", "File does not exist") wrong_path = QCoreApplication.translate("IndicatorReasons", "Wrong Directory") @@ -161,7 +162,7 @@ class IndicatorLineEdit(QWidget): def __init__( self, text: str = "", - ph_text: str = "", + placeholder: str = "", completer: QCompleter = None, edit_func: Callable[[str], Tuple[bool, str, str]] = None, save_func: Callable[[str], None] = None, @@ -176,7 +177,7 @@ class IndicatorLineEdit(QWidget): # Add line_edit self.line_edit = QLineEdit(self) self.line_edit.setObjectName("line_edit") - self.line_edit.setPlaceholderText(ph_text) + self.line_edit.setPlaceholderText(placeholder) self.line_edit.setSizePolicy(horiz_policy, QSizePolicy.Fixed) # Add hint_label to line_edit self.line_edit.setLayout(QHBoxLayout()) @@ -199,7 +200,7 @@ class IndicatorLineEdit(QWidget): self.indicator_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.layout.addWidget(self.indicator_label) - if not ph_text: + if not placeholder: _translate = QCoreApplication.translate self.line_edit.setPlaceholderText( _translate(self.__class__.__name__, "Default") @@ -318,9 +319,12 @@ class PathEdit(IndicatorLineEdit): self.compl_model.setIconProvider(PathEditIconProvider()) self.compl_model.setRootPath(path) self.completer.setModel(self.compl_model) + + edit_func = self._wrap_edit_function(edit_func) + super(PathEdit, self).__init__( text=path, - ph_text=placeholder, + placeholder=placeholder, completer=self.completer, edit_func=edit_func, save_func=save_func, @@ -357,6 +361,13 @@ class PathEdit(IndicatorLineEdit): self.line_edit.setText(names[0]) self.compl_model.setRootPath(names[0]) + def _wrap_edit_function(self, edit_function: Callable[[str], Tuple[bool, str, str]]): + if edit_function: + return lambda text: edit_function(os.path.expanduser(text) + if text.startswith("~") else text) + else: + return edit_function + class SideTabBar(QTabBar): def __init__(self, parent=None): diff --git a/rare/utils/utils.py b/rare/utils/utils.py index 0df9d06a..f9043b18 100644 --- a/rare/utils/utils.py +++ b/rare/utils/utils.py @@ -451,14 +451,16 @@ class WineResolverSignals(QObject): class WineResolver(QRunnable): - def __init__(self, path: str, app_name: str, core: LegendaryCore): + def __init__(self, path: str, app_name: str): super(WineResolver, self).__init__() self.signals = WineResolverSignals() self.setAutoDelete(True) self.wine_env = os.environ.copy() + core = LegendaryCoreSingleton() self.wine_env.update(core.get_app_environment(app_name)) self.wine_env["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;" self.wine_env["DISPLAY"] = "" + self.wine_binary = core.lgd.config.get( app_name, "wine_executable",