1
0
Fork 0
mirror of synced 2024-06-26 10:11:19 +12:00
Rare/rare/components/tabs/games/game_info/game_settings.py
2022-03-14 17:23:54 +01:00

374 lines
14 KiB
Python

import os
import platform
from logging import getLogger
from pathlib import Path
from typing import Tuple
from PyQt5.QtCore import QSettings, QThreadPool, Qt, QSize
from PyQt5.QtWidgets import (
QWidget,
QFileDialog,
QMessageBox,
QLabel,
QPushButton,
QSizePolicy, QHBoxLayout,
)
from legendary.core import LegendaryCore
from legendary.models.game import InstalledGame, Game
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.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
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):
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()
self.launch_settings_group.layout().addRow(
QLabel("Wrapper"), self.wrapper_settings
)
self.cloud_save_path_edit = PathEdit(
"",
file_type=QFileDialog.DirectoryOnly,
ph_text=self.tr("Cloud save path"),
edit_func=lambda text: (os.path.exists(text), text, PathEdit.reasons.dir_not_exist),
save_func=self.save_save_path,
)
self.cloud_layout.addRow(
QLabel(self.tr("Save path")), self.cloud_save_path_edit
)
self.compute_save_path_button = QPushButton(
icon("fa.magic"), self.tr("Auto compute save path")
)
self.compute_save_path_button.setSizePolicy(
QSizePolicy.Maximum, QSizePolicy.Fixed
)
self.compute_save_path_button.clicked.connect(self.compute_save_path)
self.cloud_layout.addRow(None, self.compute_save_path_button)
self.offline.currentIndexChanged.connect(
lambda x: self.update_combobox(x, "offline")
)
self.skip_update.currentIndexChanged.connect(
lambda x: self.update_combobox(x, "skip_update_check")
)
self.cloud_sync.stateChanged.connect(
lambda: self.settings.setValue(
f"{self.game.app_name}/auto_sync_cloud", self.cloud_sync.isChecked()
)
)
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,
)
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_contents_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"))
def compute_save_path(self):
if (
self.core.is_installed(self.game.app_name)
and self.game.supports_cloud_saves
):
try:
new_path = self.core.get_save_path(self.game.app_name)
except Exception as e:
logger.warning(str(e))
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)
)
QThreadPool.globalInstance().start(resolver)
return
else:
self.cloud_save_path_edit.setText(new_path)
def wine_resolver_finished(self, path, app_name):
logger.info(
f"Wine resolver finished for {app_name}. Computed save path: {path}"
)
if app_name == self.game.app_name:
self.cloud_save_path_edit.setDisabled(False)
self.compute_save_path_button.setDisabled(False)
if path and not os.path.exists(path):
try:
os.makedirs(path)
except PermissionError:
self.cloud_save_path_edit.setText("")
QMessageBox.warning(
None,
"Error",
self.tr(
"Error while launching {}. No permission to create {}"
).format(self.game.app_title, path),
)
return
if not path:
self.cloud_save_path_edit.setText("")
return
self.cloud_save_path_edit.setText(path)
elif path:
igame = self.core.get_installed_game(app_name)
igame.save_path = path
self.core.lgd.set_installed_game(app_name, igame)
def save_save_path(self, text):
if self.game.supports_cloud_saves and self.change:
self.igame.save_path = text
self.core.lgd.set_installed_game(self.igame.app_name, self.igame)
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 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):
self.change = False
self.game = self.core.get_game(app_name)
self.igame = self.core.get_installed_game(self.game.app_name)
if self.igame:
if self.igame.can_run_offline:
offline = self.core.lgd.config.get(
self.game.app_name, "offline", fallback="unset"
)
if offline == "true":
self.offline.setCurrentIndex(1)
elif offline == "false":
self.offline.setCurrentIndex(2)
else:
self.offline.setCurrentIndex(0)
self.offline.setEnabled(True)
else:
self.offline.setEnabled(False)
else:
self.offline.setEnabled(False)
skip_update = self.core.lgd.config.get(
self.game.app_name, "skip_update_check", fallback="unset"
)
if skip_update == "true":
self.skip_update.setCurrentIndex(1)
elif skip_update == "false":
self.skip_update.setCurrentIndex(2)
else:
self.skip_update.setCurrentIndex(0)
self.game_title.setText(f"<h2>{self.game.app_title}</h2>")
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.extra_wrappers.get("proton", "").replace('"', "")
if proton and "proton" in proton:
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=self.tr("Please select path for proton prefix"),
)
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("")
else:
self.cloud_group.setEnabled(True)
sync_cloud = self.settings.value(
f"{self.game.app_name}/auto_sync_cloud", True, bool
)
self.cloud_sync.setChecked(sync_cloud)
if self.igame.save_path:
self.cloud_save_path_edit.setText(self.igame.save_path)
else:
self.cloud_save_path_edit.setText("")
self.launch_params.setText(
self.core.lgd.config.get(self.game.app_name, "start_params", fallback="")
)
self.override_exe_edit.setText(
self.core.lgd.config.get(self.game.app_name, "override_exe", fallback="")
)
self.change = True
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)