feat: added initial exe settings
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -1674,3 +1674,286 @@ class WinetricksDialog(QDialog):
|
||||
if self.input_manager:
|
||||
self.input_manager.disable_winetricks_mode()
|
||||
super().reject()
|
||||
|
||||
class ExeSettingsDialog(QDialog):
|
||||
def __init__(self, parent=None, theme=None, exe_path=None):
|
||||
super().__init__(parent)
|
||||
self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config())
|
||||
self.exe_path = exe_path
|
||||
if not self.exe_path:
|
||||
logger.error("Exe path not provided")
|
||||
return
|
||||
self.portproton_path = get_portproton_location()
|
||||
if self.portproton_path is None:
|
||||
logger.error("PortProton location not found")
|
||||
return
|
||||
base_path = os.path.join(self.portproton_path, "data")
|
||||
self.start_sh = os.path.join(base_path, "scripts", "start.sh")
|
||||
self.ppdb_path = self.exe_path + ".ppdb" if not self.exe_path.endswith('.ppdb') else self.exe_path
|
||||
self.current_settings = {}
|
||||
self.value_widgets = {}
|
||||
self.original_values = {}
|
||||
self.available_keys = set()
|
||||
self.branch_name = _("Unknown")
|
||||
|
||||
self.setWindowTitle(_("Exe Settings"))
|
||||
self.setModal(True)
|
||||
self.resize(900, 600)
|
||||
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE + self.theme.MESSAGE_BOX_STYLE)
|
||||
|
||||
self.init_toggle_settings()
|
||||
self.setup_ui()
|
||||
|
||||
# Find input_manager and main_window
|
||||
self.input_manager = None
|
||||
self.main_window = None
|
||||
parent = self.parent()
|
||||
while parent:
|
||||
if hasattr(parent, 'input_manager'):
|
||||
self.input_manager = cast("MainWindow", parent).input_manager
|
||||
self.main_window = parent
|
||||
parent = parent.parent()
|
||||
|
||||
self.current_theme_name = read_theme_from_config()
|
||||
|
||||
# Create hints widget using common function
|
||||
self.hints_widget, self.hints_labels = create_dialog_hints_widget(
|
||||
self.theme, self.main_window, self.input_manager, context='winetricks'
|
||||
)
|
||||
self.main_layout.addWidget(self.hints_widget)
|
||||
|
||||
# Connect signals
|
||||
if self.input_manager:
|
||||
self.input_manager.button_event.connect(
|
||||
lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name)
|
||||
)
|
||||
self.input_manager.dpad_moved.connect(
|
||||
lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name)
|
||||
)
|
||||
update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name)
|
||||
|
||||
# Load current settings (includes list-db)
|
||||
self.load_current_settings()
|
||||
|
||||
def init_toggle_settings(self):
|
||||
"""Initialize predefined toggle settings with descriptions."""
|
||||
self.toggle_settings = {
|
||||
'PW_MANGOHUD': _("Using FPS and system load monitoring (Turns on and off by the key combination - right Shift + F12)"),
|
||||
'PW_MANGOHUD_USER_CONF': _("Forced use of MANGOHUD system settings (GOverlay, etc.)"),
|
||||
'PW_VKBASALT': _("Enable vkBasalt by default to improve graphics in games running on Vulkan. (The HOME hotkey disables vkbasalt)"),
|
||||
'PW_VKBASALT_USER_CONF': _("Forced use of VKBASALT system settings (GOverlay, etc.)"),
|
||||
'PW_DGVOODOO2': _("Enable dgVoodoo2. Forced use all dgVoodoo2 libs (Glide 2.11-3.1, DirectDraw 1-7, Direct3D 2-9) on all 3D API."),
|
||||
'PW_GAMESCOPE': _("Super + F : Toggle fullscreen\nSuper + N : Toggle nearest neighbour filtering\nSuper + U : Toggle FSR upscaling\nSuper + Y : Toggle NIS upscaling\nSuper + I : Increase FSR sharpness by 1\nSuper + O : Decrease FSR sharpness by 1\nSuper + S : Take screenshot (currently goes to /tmp/gamescope_DATE.png)\nSuper + G : Toggle keyboard grab\nSuper + C : Update clipboard"),
|
||||
'PW_USE_ESYNC': _("Enable in-process synchronization primitives based on eventfd."),
|
||||
'PW_USE_FSYNC': _("Enable futex-based in-process synchronization primitives."),
|
||||
'PW_USE_NTSYNC': _("Enable in-process synchronization via the Linux ntsync driver."),
|
||||
'PW_USE_RAY_TRACING': _("Enable vkd3d support - Ray Tracing"),
|
||||
'PW_USE_NVAPI_AND_DLSS': _("Enable DLSS on supported NVIDIA graphics cards"),
|
||||
'PW_USE_OPTISCALER': _("Enable OptiScaler (replacement upscaler / frame generator)"),
|
||||
'PW_USE_LS_FRAME_GEN': _("Enable Lossless Scaling frame generation (experimental)"),
|
||||
'PW_WINE_FULLSCREEN_FSR': _("FSR upscaling in fullscreen with ProtonGE below native resolution"),
|
||||
'PW_HIDE_NVIDIA_GPU': _("Disguise all NVIDIA GPU features"),
|
||||
'PW_VIRTUAL_DESKTOP': _("Run the application in WINE virtual desktop"),
|
||||
'PW_USE_TERMINAL': _("Run the application in a terminal"),
|
||||
'PW_GUI_DISABLED_CS': _("Disable startup mode and WINE version selector window"),
|
||||
'PW_USE_GAMEMODE': _("Use system GameMode for performance optimization"),
|
||||
'PW_USE_D3D_EXTRAS': _("Enable forced use of third-party DirectX libraries"),
|
||||
'PW_FIX_VIDEO_IN_GAME': _("Fix pink-tinted video playback in some games"),
|
||||
'PW_REDUCE_PULSE_LATENCY': _("Reduce PulseAudio latency to fix intermittent sound"),
|
||||
'PW_USE_US_LAYOUT': _("Force US keyboard layout"),
|
||||
'PW_USE_GSTREAMER': _("Use GStreamer for in-game clips (WMF support)"),
|
||||
'PW_USE_SHADER_CACHE': _("Use WINE shader caching"),
|
||||
'PW_USE_WINE_DXGI': _("Force use of built-in DXGI library"),
|
||||
'PW_USE_EAC_AND_BE': _("Enable Easy Anti-Cheat and BattlEye runtimes"),
|
||||
'PW_USE_SYSTEM_VK_LAYERS': _("Use system Vulkan layers (MangoHud, vkBasalt, OBS, etc.)"),
|
||||
'PW_USE_OBS_VKCAPTURE': _("Enable OBS Studio capture via obs-vkcapture"),
|
||||
'PW_DISABLE_COMPOSITING': _("Disable desktop compositing for performance"),
|
||||
'PW_USE_RUNTIME': _("Use container launch mode (recommended default)"),
|
||||
'PW_DINPUT_PROTOCOL': _("Force DirectInput protocol instead of XInput"),
|
||||
'PW_USE_NATIVE_WAYLAND': _("Enable experimental native Wayland support"),
|
||||
'PW_USE_DXVK_HDR': _("Enable HDR settings under native Wayland"),
|
||||
'PW_USE_GALLIUM_ZINK': _("Use Gallium Zink (OpenGL via Vulkan)"),
|
||||
'PW_USE_GALLIUM_NINE': _("Use Gallium Nine (native DirectX 9 for Mesa)"),
|
||||
'PW_USE_WINED3D_VULKAN': _("Use WineD3D Vulkan backend (Damavand)"),
|
||||
'PW_USE_SUPPLIED_DXVK_VKD3D': _("Use bundled dxvk/vkd3d from Wine/Proton"),
|
||||
'PW_USE_SAREK_ASYNC': _("Use async dxvk-sarek (experimental)")
|
||||
}
|
||||
|
||||
def setup_ui(self):
|
||||
"""Set up the user interface."""
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
self.main_layout.setSpacing(10)
|
||||
|
||||
# Метка с текущей веткой (STABLE / DEVEL)
|
||||
self.branch_label = QLabel(_("Detected branch: Unknown"))
|
||||
self.branch_label.setStyleSheet("font-weight: bold;")
|
||||
self.main_layout.addWidget(self.branch_label)
|
||||
|
||||
# Таблица настроек
|
||||
self.settings_table = QTableWidget()
|
||||
self.settings_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
self.settings_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.settings_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.settings_table.setColumnCount(3)
|
||||
self.settings_table.setHorizontalHeaderLabels([_("Setting"), _("Value"), _("Description")])
|
||||
self.settings_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.settings_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||
self.settings_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
|
||||
self.settings_table.horizontalHeader().resizeSection(1, 100)
|
||||
self.settings_table.setWordWrap(True)
|
||||
self.settings_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.settings_table.setTextElideMode(Qt.TextElideMode.ElideNone)
|
||||
self.settings_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE)
|
||||
self.main_layout.addWidget(self.settings_table)
|
||||
|
||||
# Кнопки
|
||||
button_layout = QHBoxLayout()
|
||||
self.apply_button = AutoSizeButton(_("Apply"), icon=theme_manager.get_icon("apply"))
|
||||
self.cancel_button = AutoSizeButton(_("Cancel"), icon=theme_manager.get_icon("cancel"))
|
||||
self.apply_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
|
||||
self.cancel_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
|
||||
button_layout.addWidget(self.apply_button)
|
||||
button_layout.addWidget(self.cancel_button)
|
||||
self.main_layout.addLayout(button_layout)
|
||||
|
||||
self.apply_button.clicked.connect(self.apply_changes)
|
||||
self.cancel_button.clicked.connect(self.reject)
|
||||
|
||||
def load_current_settings(self):
|
||||
"""Load available toggles first, then current settings."""
|
||||
process = QProcess(self)
|
||||
process.finished.connect(self.on_list_db_finished)
|
||||
process.start(self.start_sh, ["cli", "--list-db"])
|
||||
|
||||
def on_list_db_finished(self, exit_code, exit_status):
|
||||
"""Handle --list-db output and extract available keys."""
|
||||
process = cast(QProcess, self.sender())
|
||||
self.available_keys = set()
|
||||
if exit_code == 0 and exit_status == QProcess.ExitStatus.NormalExit:
|
||||
output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore')
|
||||
for line in output.splitlines():
|
||||
if "Branch in used:" in line:
|
||||
self.branch_name = line.split(":", 1)[1].strip()
|
||||
self.branch_label.setText(_("Detected branch: ") + self.branch_name)
|
||||
for token in line.split():
|
||||
if token.startswith("PW_"):
|
||||
self.available_keys.add(token.strip())
|
||||
|
||||
# Показываем только пересечение
|
||||
self.available_keys &= set(self.toggle_settings.keys())
|
||||
logger.debug(f"Filtered available keys (intersection): {self.available_keys}")
|
||||
else:
|
||||
logger.warning("Failed to get --list-db output; showing all toggles")
|
||||
self.available_keys = set(self.toggle_settings.keys())
|
||||
|
||||
# Загружаем текущие настройки
|
||||
process = QProcess(self)
|
||||
process.finished.connect(self.on_show_ppdb_finished)
|
||||
process.start(self.start_sh, ["cli", "--show-ppdb", self.ppdb_path])
|
||||
|
||||
def on_show_ppdb_finished(self, exit_code, exit_status):
|
||||
"""Handle --show-ppdb output."""
|
||||
process = cast(QProcess, self.sender())
|
||||
if exit_code != 0 or exit_status != QProcess.ExitStatus.NormalExit:
|
||||
logger.warning("Failed to load settings, using defaults")
|
||||
else:
|
||||
output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore').strip()
|
||||
self.current_settings = {}
|
||||
for line in output.split('\n'):
|
||||
if '=' in line and line.strip().startswith('PW_'):
|
||||
key, val = line.split('=', 1)
|
||||
self.current_settings[key.strip()] = val.strip()
|
||||
logger.debug(f"Loaded current settings: {self.current_settings}")
|
||||
self.populate_table()
|
||||
|
||||
def populate_table(self):
|
||||
"""Populate the table with settings that are available in both lists."""
|
||||
self.settings_table.setRowCount(0)
|
||||
self.value_widgets.clear()
|
||||
self.original_values.clear()
|
||||
self.settings_table.verticalHeader().setVisible(False)
|
||||
|
||||
visible_keys = sorted(self.available_keys) if self.available_keys else sorted(self.toggle_settings.keys())
|
||||
|
||||
for toggle in visible_keys:
|
||||
description = self.toggle_settings.get(toggle)
|
||||
if not description:
|
||||
continue
|
||||
|
||||
row = self.settings_table.rowCount()
|
||||
self.settings_table.insertRow(row)
|
||||
|
||||
name_item = QTableWidgetItem(toggle)
|
||||
name_item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled)
|
||||
self.settings_table.setItem(row, 0, name_item)
|
||||
|
||||
current_val = self.current_settings.get(toggle, '0')
|
||||
checkbox = QTableWidgetItem()
|
||||
checkbox.setFlags(checkbox.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||
checkbox.setCheckState(Qt.CheckState.Checked if current_val == '1' else Qt.CheckState.Unchecked)
|
||||
checkbox.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.settings_table.setItem(row, 1, checkbox)
|
||||
|
||||
desc_item = QTableWidgetItem(description)
|
||||
desc_item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled)
|
||||
desc_item.setToolTip(description)
|
||||
desc_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
||||
self.settings_table.setItem(row, 2, desc_item)
|
||||
|
||||
self.value_widgets[(row, 1)] = checkbox
|
||||
self.original_values[toggle] = current_val
|
||||
|
||||
self.settings_table.resizeRowsToContents()
|
||||
if self.settings_table.rowCount() > 0:
|
||||
self.settings_table.setCurrentCell(0, 0)
|
||||
self.settings_table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
|
||||
def apply_changes(self):
|
||||
"""Apply changes by collecting diffs and running --edit-db."""
|
||||
changes = []
|
||||
for key, orig_val in self.original_values.items():
|
||||
row = -1
|
||||
for r in range(self.settings_table.rowCount()):
|
||||
item0 = self.settings_table.item(r, 0)
|
||||
if item0 and item0.text() == key:
|
||||
row = r
|
||||
break
|
||||
if row == -1:
|
||||
continue
|
||||
|
||||
item = self.settings_table.item(row, 1)
|
||||
if not item:
|
||||
continue
|
||||
|
||||
new_val = '1' if item.checkState() == Qt.CheckState.Checked else '0'
|
||||
if new_val != orig_val:
|
||||
changes.append(f"{key}={new_val}")
|
||||
|
||||
if not changes:
|
||||
QMessageBox.information(self, _("Info"), _("No changes to apply."))
|
||||
return
|
||||
|
||||
process = QProcess(self)
|
||||
process.finished.connect(self.on_edit_db_finished)
|
||||
args = ["cli", "--edit-db", self.exe_path] + changes
|
||||
process.start(self.start_sh, args)
|
||||
self.apply_button.setEnabled(False)
|
||||
|
||||
def on_edit_db_finished(self, exit_code, exit_status):
|
||||
"""Handle --edit-db output."""
|
||||
process = cast(QProcess, self.sender())
|
||||
self.apply_button.setEnabled(True)
|
||||
if exit_code != 0 or exit_status != QProcess.ExitStatus.NormalExit:
|
||||
error_output = bytes(process.readAllStandardError().data()).decode('utf-8', 'ignore')
|
||||
QMessageBox.warning(self, _("Error"), _("Failed to apply changes. Check logs."))
|
||||
logger.error(f"Failed to apply changes: {error_output}")
|
||||
else:
|
||||
self.load_current_settings()
|
||||
QMessageBox.information(self, _("Success"), _("Settings updated successfully."))
|
||||
|
||||
def closeEvent(self, event):
|
||||
super().closeEvent(event)
|
||||
|
||||
def reject(self):
|
||||
super().reject()
|
||||
|
||||
@@ -8,7 +8,7 @@ import psutil
|
||||
import re
|
||||
|
||||
from portprotonqt.logger import get_logger
|
||||
from portprotonqt.dialogs import AddGameDialog, FileExplorer, WinetricksDialog
|
||||
from portprotonqt.dialogs import AddGameDialog, FileExplorer, WinetricksDialog, ExeSettingsDialog
|
||||
from portprotonqt.game_card import GameCard
|
||||
from portprotonqt.animations import DetailPageAnimations
|
||||
from portprotonqt.custom_widgets import ClickableLabel, AutoSizeButton, NavLabel, FlowLayout
|
||||
@@ -2326,6 +2326,14 @@ class MainWindow(QMainWindow):
|
||||
def darkenColor(self, color, factor=200):
|
||||
return color.darker(factor)
|
||||
|
||||
def open_exe_settings(self, exe_path):
|
||||
"""Open the ExeSettingsDialog for the given executable."""
|
||||
if not os.path.exists(exe_path):
|
||||
QMessageBox.warning(self, _("Error"), _("Executable not found: {0}").format(exe_path))
|
||||
return
|
||||
dialog = ExeSettingsDialog(self, self.theme, exe_path)
|
||||
dialog.exec()
|
||||
|
||||
def openGameDetailPage(self, name, description, cover_path=None, appid="", exec_line="", controller_support="", last_launch="", formatted_playtime="", protondb_tier="", game_source="", anticheat_status=""):
|
||||
detailPage = QWidget()
|
||||
self._animations = {}
|
||||
@@ -2628,8 +2636,6 @@ class MainWindow(QMainWindow):
|
||||
|
||||
clear_layout(hltbLayout)
|
||||
|
||||
|
||||
|
||||
has_data = False
|
||||
|
||||
if main_story_time is not None:
|
||||
@@ -2713,6 +2719,14 @@ class MainWindow(QMainWindow):
|
||||
playButton.clicked.connect(lambda: self.toggleGame(exec_line, playButton))
|
||||
detailsLayout.addWidget(playButton, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
# Settings button
|
||||
settings_icon = self.theme_manager.get_icon("settings")
|
||||
settings_button = AutoSizeButton(_("Settings"), icon=settings_icon)
|
||||
settings_button.setFixedSize(120, 40)
|
||||
settings_button.setStyleSheet(self.theme.PLAY_BUTTON_STYLE)
|
||||
settings_button.clicked.connect(lambda: self.open_exe_settings(file_to_check))
|
||||
detailsLayout.addWidget(settings_button, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
contentFrameLayout.addWidget(detailsWidget)
|
||||
mainLayout.addStretch()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user