1
0
Fork 0
mirror of synced 2024-06-03 03:04:42 +12:00

Implement env_vars

This commit is contained in:
aznd 2022-03-01 22:36:16 +01:00
parent c2a117e226
commit c84cc388fb
3 changed files with 284 additions and 251 deletions

View file

@ -1,250 +0,0 @@
from PyQt5.QtWidgets import QAbstractButton, QGroupBox, QPushButton, QTableWidgetItem, QMessageBox
import qtawesome
from rare.ui.components.tabs.games.env_vars import Ui_EnvVars
from rare.utils import config_helper
from rare.shared import LegendaryCoreSingleton
from PyQt5.QtCore import Qt, QFileSystemWatcher
class EnvVars(QGroupBox, Ui_EnvVars):
def __init__(self, parent):
super(EnvVars, self).__init__(parent=parent)
self.setupUi(self)
self.app_name = None
self.core = LegendaryCoreSingleton()
self.latest_item = None
self.list_of_readonly = ["STEAM_COMPAT_DATA_PATH", "DXVK_HUD", "WINEPREFIX"]
self.warn_msg = self.tr("Readonly, please edit this via the appropriate setting above.")
self.setup_file_watcher()
# We use this function to keep an eye on the config.
# When the user uses for example the wineprefix settings, we need to update the table.
# With this function, when the config file changes, we update the table.
def setup_file_watcher(self):
self.config_file_watcher = QFileSystemWatcher([self.core.lgd.config_path], self)
self.config_file_watcher.fileChanged.connect(self.import_env_vars)
def compare_config_and_table(self):
list_of_keys_in_table = []
row_count = self.env_vars_table.rowCount()
for i in range(row_count):
item = self.env_vars_table.item(i, 0)
try:
list_of_keys_in_table.append(item.text())
except AttributeError:
pass
list_of_keys_in_config = []
try:
for key in self.core.lgd.config[f"{self.app_name}.env"]:
list_of_keys_in_config.append(key)
except KeyError:
pass
def import_env_vars(self):
self.env_vars_table.disconnect()
self.env_vars_table.clearContents()
list_of_keys = []
list_of_values = []
# First, we try to get all keys and values from the config
try:
for key in self.core.lgd.config[f"{self.app_name}.env"]:
list_of_keys.append(key)
for i in list_of_keys:
list_of_values.append(self.core.lgd.config[f"{self.app_name}.env"][i])
except KeyError:
pass
# We count how many keys we have and insert new lines
# (as many as we need).
# Each iteration we have to create a new QTableWidgetItem object,
# else we segfault. (For using the same object in multiple references.)
count = len(list_of_keys)
for i in range(count):
self.env_vars_table.insertRow(i)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(i, trash_icon)
# We set the row count to the length of the list_of_keys
if len(list_of_keys) == 0:
self.env_vars_table.setRowCount(1)
else:
self.env_vars_table.setRowCount(len(list_of_keys))
for index, val in enumerate(list_of_keys):
new_item = QTableWidgetItem()
new_item.setText(val)
if new_item.text() in self.list_of_readonly:
new_item.setFlags(new_item.flags() ^ Qt.ItemIsEnabled)
new_item.setToolTip(self.warn_msg)
self.env_vars_table.setItem(index, 0, new_item)
for index, val in enumerate(list_of_values):
new_item = QTableWidgetItem()
new_item.setText(val)
# We need to check if the first_item is in the list of readonly vars.
# If yes, we also need to disable column 1.
first_item = self.env_vars_table.item(index, 0)
if first_item.text() in self.list_of_readonly:
new_item.setFlags(new_item.flags() ^ Qt.ItemIsEnabled)
new_item.setToolTip(self.warn_msg)
self.env_vars_table.setItem(index, 1, new_item)
row_count = self.env_vars_table.rowCount()
last_item = self.env_vars_table.item(row_count - 1, 0)
if last_item is not None:
self.env_vars_table.insertRow(row_count)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(row_count, trash_icon)
new_thing = QTableWidgetItem()
new_thing.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(0, new_thing)
self.compare_config_and_table()
# We need to call this at the end, since at startup we import the
# config and update the table. Thus, the cellChanged signal reacts, but
# we don't want that. Maybe we should only call this once, at startup?
# Like this we always connect at every update_game.
self.env_vars_table.cellChanged.connect(self.update_env_vars)
self.env_vars_table.verticalHeader().sectionClicked.connect(self.remove_row)
def update_env_vars(self, row):
row_count = self.env_vars_table.rowCount()
first_item = self.env_vars_table.item(row, 0)
second_item = self.env_vars_table.item(row, 1)
list_of_keys = []
try:
for key in self.core.lgd.config[f"{self.app_name}.env"]:
list_of_keys.append(key)
except KeyError:
pass
list_of_keys_in_table = []
for i in range(row_count):
item = self.env_vars_table.item(i, 0)
try:
list_of_keys_in_table.append(item.text())
except AttributeError:
pass
missing_item = list(set(list_of_keys) - set(list_of_keys_in_table))
if len(missing_item) != 0:
config_helper.remove_option(f"{self.app_name}.env", missing_item[0])
# A env var always needs to have a key.
# If it's none, we return.
if first_item is None:
return
if first_item.text() in self.list_of_readonly:
error_dialog = QMessageBox()
error_dialog.setText(self.tr("Please don't manually add this environment variable. Use the appropriate game setting above."))
error_dialog.exec()
first_item.setText("")
return
if first_item.text():
if first_item.text() in list_of_keys:
ask_user = QMessageBox()
ask_user.setText(self.tr("The config already contains this environment variable."))
ask_user.setInformativeText(self.tr("Do you want to keep the newer one or the older one?"))
ask_user.addButton(QPushButton("Keep the newer one"), QMessageBox.YesRole)
ask_user.addButton(QPushButton("Keep the older one"), QMessageBox.NoRole)
response = ask_user.exec()
if response == 0:
# We delete the old one
item_to_be_deleted = self.env_vars_table.findItems(first_item.text(), Qt.MatchExactly)
row_to_be_removed = item_to_be_deleted[0].row()
self.env_vars_table.removeRow(row_to_be_removed)
config_helper.remove_option(f"{self.app_name}.env", first_item.text())
elif response == 1:
first_item.text() == ""
if second_item is not None:
second_item.text() == ""
return
# When the second_item is None, we just use an empty string for the value.
if second_item is None:
if self.latest_item in list_of_keys:
config_helper.remove_option(f"{self.app_name}.env", self.latest_item)
config_helper.save_config()
config_helper.add_option(f"{self.app_name}.env", first_item.text(), "")
config_helper.save_config()
else:
if self.latest_item in list_of_keys:
config_helper.remove_option(f"{self.app_name}.env", self.latest_item)
config_helper.save_config()
config_helper.add_option(
f"{self.app_name}.env",
first_item.text(),
second_item.text()
)
config_helper.save_config()
row_count = self.env_vars_table.rowCount()
last_item = self.env_vars_table.item(row_count - 1, 0)
if last_item is not None:
if last_item.text():
self.env_vars_table.insertRow(row_count)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(row_count, trash_icon)
def remove_row(self, index):
# The user also should be able to delete the row,
# even if rowCount() is lower than 1, but then the row
# should be just cleared. (And not deleted)
first_item = self.env_vars_table.item(index, 0)
# The first item needs to have some text, else we don't delete it.
if first_item is None:
return
# If the user tries to delete one of the readonly vars, we show a message and return.
if first_item.text() in self.list_of_readonly:
error_dialog = QMessageBox()
error_dialog.setText(self.tr("Please use the appropriate setting above to remove this key."))
error_dialog.exec()
return
if first_item is not None:
self.env_vars_table.removeRow(index)
try:
list_of_keys = []
for key in self.core.lgd.config[f"{self.app_name}.env"]:
list_of_keys.append(key)
config_helper.remove_option(f"{self.app_name}.env", list_of_keys[index])
except (KeyError, IndexError):
pass
def keyPressEvent(self, e):
if e.key() == Qt.Key_Delete or e.key() == Qt.Key_Backspace:
selected_items = self.env_vars_table.selectedItems()
for i in selected_items:
config_helper.remove_option(f"{self.app_name}.env", i.text())
self.env_vars_table.removeRow(i.row())
elif e.key() == Qt.Key_Escape:
self.parent().layout().setCurrentIndex(0)
def update_game(self, app_name):
self.app_name = app_name
self.import_env_vars()

View file

@ -19,7 +19,7 @@ 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.components.tabs.games.env_vars import EnvVars
from rare.components.tabs.settings.widgets.env_vars import EnvVars
from rare.utils import config_helper
from rare.utils.extra_widgets import PathEdit
from rare.utils.utils import WineResolver, get_raw_save_path
@ -142,6 +142,7 @@ class GameSettings(QWidget, Ui_GameSettings):
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)
@ -365,6 +366,8 @@ class GameSettings(QWidget, Ui_GameSettings):
self.env_vars.update_game(app_name)
class LinuxAppSettings(LinuxSettings):
def __init__(self):
super(LinuxAppSettings, self).__init__()

View file

@ -0,0 +1,280 @@
from logging import getLogger
import qtawesome
from PyQt5.QtCore import Qt, QFileSystemWatcher
from PyQt5.QtWidgets import QGroupBox, QTableWidgetItem, QMessageBox, QPushButton
from rare.shared import LegendaryCoreSingleton
from rare.ui.components.tabs.games.env_vars import Ui_EnvVars
from rare.utils import config_helper
logger = getLogger("EnvVars")
class EnvVars(QGroupBox, Ui_EnvVars):
def __init__(self, parent):
super(EnvVars, self).__init__(parent=parent)
self.setupUi(self)
self.app_name = None
self.core = LegendaryCoreSingleton()
self.latest_item = None
self.list_of_readonly = ["STEAM_COMPAT_DATA_PATH", "DXVK_HUD", "WINEPREFIX", "STEAM_COMPAT_CLIENT_INSTALL_PATH"]
self.warn_msg = self.tr("Readonly, please edit this via the appropriate setting above.")
self.setup_file_watcher()
self.env_vars_table.cellChanged.connect(self.update_env_vars)
self.env_vars_table.verticalHeader().sectionClicked.connect(self.remove_row)
self.env_vars_table.setProperty("no_kinetic_scroll", True)
# We use this function to keep an eye on the config.
# When the user uses for example the wineprefix settings, we need to update the table.
# With this function, when the config file changes, we update the table.
def setup_file_watcher(self):
self.config_file_watcher = QFileSystemWatcher([self.core.lgd.config_path], self)
self.config_file_watcher.fileChanged.connect(self.import_env_vars)
def import_env_vars(self):
self.config_file_watcher.blockSignals(True)
self.env_vars_table.blockSignals(True)
self.env_vars_table.clearContents()
# If the config file doesnt have an env var section, we just set RowCount to 1 and return.
if not self.core.lgd.config.has_section(f"{self.app_name}.env"):
self.env_vars_table.setRowCount(1)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(0, trash_icon)
self.env_vars_table.blockSignals(False)
return
# We count how many keys we have and insert new lines
# (as many as we need).
self.env_vars_table.setRowCount(len(self.core.lgd.config[f"{self.app_name}.env"]) + 1)
# Each iteration we have to create a new QTableWidgetItem object,
# else we segfault. (For using the same object in multiple references.)
for i, (key, value) in enumerate(self.core.lgd.config[f"{self.app_name}.env"].items()):
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(i, trash_icon)
key_item = QTableWidgetItem()
key_item.setText(key)
self.env_vars_table.setItem(i, 0, key_item)
value_item = QTableWidgetItem()
value_item.setText(value)
self.env_vars_table.setItem(i, 1, value_item)
if key in self.list_of_readonly:
key_item.setFlags(key_item.flags() ^ Qt.ItemIsEnabled)
key_item.setToolTip(self.warn_msg)
value_item.setFlags(value_item.flags() ^ Qt.ItemIsEnabled)
value_item.setToolTip(self.warn_msg)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(self.env_vars_table.rowCount() - 1, trash_icon)
self.config_file_watcher.blockSignals(False)
self.env_vars_table.blockSignals(False)
def update_env_vars(self, row, column):
self.config_file_watcher.blockSignals(True)
row_count = self.env_vars_table.rowCount()
key_item = self.env_vars_table.item(row, 0)
value_item = self.env_vars_table.item(row, 1)
last_item = self.env_vars_table.item(self.env_vars_table.rowCount() - 1, 0)
# get all config keys
try:
list_of_config_keys = list(self.core.lgd.config[f"{self.app_name}.env"].keys())
except KeyError:
list_of_config_keys = []
# get all table keys
list_of_keys_in_table = []
for i in range(row_count):
item = self.env_vars_table.item(i, 0)
if item:
list_of_keys_in_table.append(item.text())
missing_item = list(set(list_of_config_keys) - set(list_of_keys_in_table))
if len(missing_item) != 0:
config_helper.remove_option(f"{self.app_name}.env", missing_item[0])
# A env var always needs to have a key.
# If it's none, we return.
if key_item is None:
return
if key_item.text() in self.list_of_readonly:
error_dialog = QMessageBox()
error_dialog.setText(
self.tr("Please don't manually add this environment variable. Use the appropriate game setting above."))
error_dialog.exec()
key_item.setText("")
if value_item is not None:
value_item.setText("")
config_helper.remove_option(f"{self.app_name}.env", key_item.text())
return
if key_item.text():
if "=" in key_item.text():
error_dialog = QMessageBox()
error_dialog.setText(
self.tr("Please don't use an equal sign in an env var."))
error_dialog.exec()
key_item.setText("")
if value_item is not None:
value_item.setText("")
return
if key_item.text() in list_of_config_keys and column == 0:
ask_user = QMessageBox()
ask_user.setText(
self.tr("The config already contains this environment variable."))
ask_user.setInformativeText(
self.tr("Do you want to keep the newer one or the older one?"))
ask_user.addButton(QPushButton("Keep the newer one"), QMessageBox.YesRole)
ask_user.addButton(QPushButton("Keep the older one"), QMessageBox.NoRole)
response = ask_user.exec()
if response == 0:
if value_item is not None:
config_helper.add_option(f"{self.app_name}.env", key_item.text(), value_item.text())
else:
config_helper.add_option(f"{self.app_name}.env", key_item.text(), "")
item_to_safe = self.env_vars_table.findItems(key_item.text(), Qt.MatchExactly)
# aznd:
# This is to fix an issue where the user updates a env var, thats above the older one.
# so say if you have two env vars:
# something = newkey
# yes = oldkey
# if the user updates the something key to yes, it would delete the wrong row,
# since item_to_safe[0] is the first search result. but we dont want to delete that,
# we want to delete the second result.
# we use this simple if check to find out which case we have.
if key_item.row() < item_to_safe[0].row():
self.env_vars_table.removeRow(item_to_safe[0].row())
elif key_item.row() > item_to_safe[0].row():
self.env_vars_table.removeRow(item_to_safe[0].row())
else:
self.env_vars_table.removeRow(item_to_safe[1].row())
if last_item is not None:
self.env_vars_table.insertRow(row_count-1)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(row_count-1, trash_icon)
return
elif response == 1:
self.env_vars_table.removeRow(row)
if last_item is not None:
self.env_vars_table.insertRow(row)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(row_count-1, trash_icon)
return
# When the value_item is None, we just use an empty string for the value.
if value_item is None:
if self.latest_item in list_of_config_keys:
config_helper.remove_option(f"{self.app_name}.env", self.latest_item)
config_helper.save_config()
config_helper.add_option(f"{self.app_name}.env", key_item.text(), "")
config_helper.save_config()
else:
if self.latest_item in list_of_config_keys:
config_helper.remove_option(f"{self.app_name}.env", self.latest_item)
config_helper.save_config()
config_helper.add_option(
f"{self.app_name}.env",
key_item.text(),
value_item.text()
)
config_helper.save_config()
row_count = self.env_vars_table.rowCount()
last_item = self.env_vars_table.item(row_count - 1, 0)
if last_item is not None:
if last_item.text():
self.env_vars_table.insertRow(row_count)
trash_icon = QTableWidgetItem()
trash_icon.setIcon(qtawesome.icon("mdi.delete"))
self.env_vars_table.setVerticalHeaderItem(row_count, trash_icon)
self.config_file_watcher.blockSignals(False)
def remove_row(self, index):
self.config_file_watcher.blockSignals(True)
key_item = self.env_vars_table.item(index, 0)
# The first item needs to have some text, else we don't delete it.
if key_item is None:
return
# If the user tries to delete one of the readonly vars, we return immediately.
if key_item.text() in self.list_of_readonly:
return
if key_item is not None:
self.env_vars_table.removeRow(index)
try:
list_of_keys = []
for key in self.core.lgd.config[f"{self.app_name}.env"]:
list_of_keys.append(key)
config_helper.remove_option(f"{self.app_name}.env", list_of_keys[index])
except (KeyError, IndexError):
pass
self.config_file_watcher.blockSignals(False)
def check_if_item(self, item: QTableWidgetItem) -> bool:
item_to_check = self.env_vars_table.findItems(item.text(), Qt.MatchExactly)
if item_to_check[0].column() == 0:
return False
return True
def keyPressEvent(self, e):
if e.key() == Qt.Key_Delete or e.key() == Qt.Key_Backspace:
selected_items = self.env_vars_table.selectedItems()
item_in_table = self.env_vars_table.findItems(selected_items[0].text(), Qt.MatchExactly)
# Our first selection is in column 0. So, we have to find out if the user
# only selected keys, or keys and values. we use the check_if_item func
if item_in_table[0].column() == 0:
which_index_to_use = 1
if len(selected_items) == 0:
return
if len(selected_items) == 1:
which_index_to_use = 0
if self.check_if_item(selected_items[which_index_to_use]):
# User selected keys and values, so we skip the values
for i in selected_items[::2]:
config_helper.remove_option(f"{self.app_name}.env", i.text())
self.env_vars_table.removeRow(i.row())
else:
# user only selected keys
for i in selected_items:
config_helper.remove_option(f"{self.app_name}.env", i.text())
self.env_vars_table.removeRow(i.row())
# User only selected values, so we just set the text to ""
elif item_in_table[0].column() == 1:
[i.setText("") for i in selected_items]
elif e.key() == Qt.Key_Escape:
e.ignore()
def update_game(self, app_name):
self.app_name = app_name
self.import_env_vars()