feat(settings): added advanced
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Code check / Check code (push) Successful in 1m6s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Code check / Check code (push) Successful in 1m6s
				
			Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
		| @@ -5,9 +5,8 @@ from typing import cast, TYPE_CHECKING | |||||||
| from PySide6.QtGui import QPixmap, QIcon, QTextCursor, QColor | from PySide6.QtGui import QPixmap, QIcon, QTextCursor, QColor | ||||||
| from PySide6.QtWidgets import ( | from PySide6.QtWidgets import ( | ||||||
|     QDialog, QFormLayout, QHBoxLayout, QLabel, QVBoxLayout, QListWidget, QScrollArea, QWidget, QListWidgetItem, QSizePolicy, QApplication, QProgressBar, QScroller, |     QDialog, QFormLayout, QHBoxLayout, QLabel, QVBoxLayout, QListWidget, QScrollArea, QWidget, QListWidgetItem, QSizePolicy, QApplication, QProgressBar, QScroller, | ||||||
|     QTabWidget, QTableWidget, QHeaderView, QMessageBox, QTableWidgetItem, QTextEdit, QAbstractItemView, QStackedWidget |     QTabWidget, QTableWidget, QHeaderView, QMessageBox, QTableWidgetItem, QTextEdit, QAbstractItemView, QStackedWidget, QComboBox | ||||||
| ) | ) | ||||||
|  |  | ||||||
| from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase, QTimer, QThreadPool, QRunnable, Slot, QProcess, QProcessEnvironment | from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase, QTimer, QThreadPool, QRunnable, Slot, QProcess, QProcessEnvironment | ||||||
| from icoextract import IconExtractor, IconExtractorError | from icoextract import IconExtractor, IconExtractorError | ||||||
| from PIL import Image | from PIL import Image | ||||||
| @@ -1674,27 +1673,32 @@ class WinetricksDialog(QDialog): | |||||||
|         if self.input_manager: |         if self.input_manager: | ||||||
|             self.input_manager.disable_winetricks_mode() |             self.input_manager.disable_winetricks_mode() | ||||||
|         super().reject() |         super().reject() | ||||||
|  |  | ||||||
| class ExeSettingsDialog(QDialog): | class ExeSettingsDialog(QDialog): | ||||||
|     def __init__(self, parent=None, theme=None, exe_path=None): |     def __init__(self, parent=None, theme=None, exe_path=None): | ||||||
|         super().__init__(parent) |         super().__init__(parent) | ||||||
|         self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config()) |         self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config()) | ||||||
|         self.exe_path = exe_path |         self.exe_path = exe_path | ||||||
|         if not self.exe_path: |         if not self.exe_path: | ||||||
|             logger.error("Exe path not provided") |  | ||||||
|             return |             return | ||||||
|         self.portproton_path = get_portproton_location() |         self.portproton_path = get_portproton_location() | ||||||
|         if self.portproton_path is None: |         if self.portproton_path is None: | ||||||
|             logger.error("PortProton location not found") |             logger.error("PortProton location not found") | ||||||
|             return |             return | ||||||
|         base_path = os.path.join(self.portproton_path, "data") |         base_path = os.path.join(self.portproton_path, "data") | ||||||
|         self.start_sh = os.path.join(base_path, "scripts", "start.sh") |         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.current_settings = {} | ||||||
|         self.value_widgets = {} |         self.value_widgets = {} | ||||||
|         self.original_values = {} |         self.original_values = {} | ||||||
|  |         self.advanced_widgets = {} | ||||||
|  |         self.original_display_values = {} | ||||||
|         self.available_keys = set() |         self.available_keys = set() | ||||||
|         self.blocked_keys = set() |         self.blocked_keys = set() | ||||||
|  |         self.numa_nodes = {} | ||||||
|  |         self.is_amd = False | ||||||
|  |         self.locale_options = [] | ||||||
|  |         self.logical_core_options = [] | ||||||
|  |         self.amd_vulkan_drivers = [] | ||||||
|         self.branch_name = _("Unknown") |         self.branch_name = _("Unknown") | ||||||
|  |  | ||||||
|         self.setWindowTitle(_("Exe Settings")) |         self.setWindowTitle(_("Exe Settings")) | ||||||
| @@ -1717,25 +1721,16 @@ class ExeSettingsDialog(QDialog): | |||||||
|  |  | ||||||
|         self.current_theme_name = read_theme_from_config() |         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) |         # Load current settings (includes list-db) | ||||||
|         self.load_current_settings() |         self.load_current_settings() | ||||||
|  |  | ||||||
|  |     def _get_process_args(self, subcommand_args): | ||||||
|  |         """Get the full arguments for QProcess.start, handling flatpak separator.""" | ||||||
|  |         if self.start_sh[0] == "flatpak": | ||||||
|  |             return self.start_sh[1:] + ["--"] + subcommand_args | ||||||
|  |         else: | ||||||
|  |             return self.start_sh + subcommand_args | ||||||
|  |  | ||||||
|     def init_toggle_settings(self): |     def init_toggle_settings(self): | ||||||
|         """Initialize predefined toggle settings with descriptions.""" |         """Initialize predefined toggle settings with descriptions.""" | ||||||
|         self.toggle_settings = { |         self.toggle_settings = { | ||||||
| @@ -1786,10 +1781,15 @@ class ExeSettingsDialog(QDialog): | |||||||
|         self.main_layout.setContentsMargins(10, 10, 10, 10) |         self.main_layout.setContentsMargins(10, 10, 10, 10) | ||||||
|         self.main_layout.setSpacing(10) |         self.main_layout.setSpacing(10) | ||||||
|  |  | ||||||
|         # Метка с текущей веткой (STABLE / DEVEL) |         # Tab widget | ||||||
|         self.branch_label = QLabel(_("Detected branch: Unknown")) |         self.tab_widget = QTabWidget() | ||||||
|         self.branch_label.setStyleSheet("font-weight: bold;") |         self.main_tab = QWidget() | ||||||
|         self.main_layout.addWidget(self.branch_label) |         self.main_tab_layout = QVBoxLayout(self.main_tab) | ||||||
|  |         self.advanced_tab = QWidget() | ||||||
|  |         self.advanced_tab_layout = QVBoxLayout(self.advanced_tab) | ||||||
|  |  | ||||||
|  |         self.tab_widget.addTab(self.main_tab, _("Main")) | ||||||
|  |         self.tab_widget.addTab(self.advanced_tab, _("Advanced")) | ||||||
|  |  | ||||||
|         # Таблица настроек |         # Таблица настроек | ||||||
|         self.settings_table = QTableWidget() |         self.settings_table = QTableWidget() | ||||||
| @@ -1806,12 +1806,31 @@ class ExeSettingsDialog(QDialog): | |||||||
|         self.settings_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) |         self.settings_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) | ||||||
|         self.settings_table.setTextElideMode(Qt.TextElideMode.ElideNone) |         self.settings_table.setTextElideMode(Qt.TextElideMode.ElideNone) | ||||||
|         self.settings_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE) |         self.settings_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE) | ||||||
|         self.main_layout.addWidget(self.settings_table) |         self.main_tab_layout.addWidget(self.settings_table) | ||||||
|  |  | ||||||
|  |         # Таблица Advanced | ||||||
|  |         self.advanced_table = QTableWidget() | ||||||
|  |         self.advanced_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||||
|  |         self.advanced_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) | ||||||
|  |         self.advanced_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) | ||||||
|  |         self.advanced_table.setColumnCount(3) | ||||||
|  |         self.advanced_table.setHorizontalHeaderLabels([_("Setting"), _("Value"), _("Description")]) | ||||||
|  |         self.advanced_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) | ||||||
|  |         self.advanced_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) | ||||||
|  |         self.advanced_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) | ||||||
|  |         self.advanced_table.horizontalHeader().resizeSection(1, 200) | ||||||
|  |         self.advanced_table.setWordWrap(True) | ||||||
|  |         self.advanced_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) | ||||||
|  |         self.advanced_table.setTextElideMode(Qt.TextElideMode.ElideNone) | ||||||
|  |         self.advanced_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE) | ||||||
|  |         self.advanced_tab_layout.addWidget(self.advanced_table) | ||||||
|  |  | ||||||
|  |         self.main_layout.addWidget(self.tab_widget) | ||||||
|  |  | ||||||
|         # Кнопки |         # Кнопки | ||||||
|         button_layout = QHBoxLayout() |         button_layout = QHBoxLayout() | ||||||
|         self.apply_button = AutoSizeButton(_("Apply"), icon=theme_manager.get_icon("apply")) |         self.apply_button = AutoSizeButton(_("Apply"), icon=ThemeManager().get_icon("apply")) | ||||||
|         self.cancel_button = AutoSizeButton(_("Cancel"), icon=theme_manager.get_icon("cancel")) |         self.cancel_button = AutoSizeButton(_("Cancel"), icon=ThemeManager().get_icon("cancel")) | ||||||
|         self.apply_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) |         self.apply_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||||
|         self.cancel_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.apply_button) | ||||||
| @@ -1821,69 +1840,107 @@ class ExeSettingsDialog(QDialog): | |||||||
|         self.apply_button.clicked.connect(self.apply_changes) |         self.apply_button.clicked.connect(self.apply_changes) | ||||||
|         self.cancel_button.clicked.connect(self.reject) |         self.cancel_button.clicked.connect(self.reject) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def load_current_settings(self): |     def load_current_settings(self): | ||||||
|         """Load available toggles first, then current settings.""" |         """Load available toggles first, then current settings.""" | ||||||
|         process = QProcess(self) |         process = QProcess(self) | ||||||
|         process.finished.connect(self.on_list_db_finished) |         process.finished.connect(self.on_list_db_finished) | ||||||
|         process.start(self.start_sh, ["cli", "--list-db"]) |         process.start(self.start_sh[0], ["cli", "--list-db"]) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def on_list_db_finished(self, exit_code, exit_status): |     def on_list_db_finished(self, exit_code, exit_status): | ||||||
|         """Handle --list-db output and extract available keys.""" |         """Handle --list-db output and extract available keys and system info.""" | ||||||
|         process = cast(QProcess, self.sender()) |         process = cast(QProcess, self.sender()) | ||||||
|         self.available_keys = set() |         self.available_keys = set() | ||||||
|         self.blocked_keys = set() |         self.blocked_keys = set() | ||||||
|         if exit_code == 0 and exit_status == QProcess.ExitStatus.NormalExit: |         if exit_code == 0 and exit_status == QProcess.ExitStatus.NormalExit: | ||||||
|             output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore') |             output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore') | ||||||
|             for line in output.splitlines(): |             lines = output.splitlines() | ||||||
|                 if "Branch in used:" in line: |             self.numa_nodes = {} | ||||||
|                     self.branch_name = line.split(":", 1)[1].strip() |             self.is_amd = False | ||||||
|                     self.branch_label.setText(_("Detected branch: ") + self.branch_name) |             self.logical_core_options = [] | ||||||
|  |             self.locale_options = [] | ||||||
|  |             self.amd_vulkan_drivers = [] | ||||||
|  |             for line in lines: | ||||||
|  |                 line_stripped = line.strip() | ||||||
|  |                 if not line_stripped: | ||||||
|                     continue |                     continue | ||||||
|                 stripped_line = line.strip() |                 if re.match(r'^[A-Z_0-9]+=[^=]+$', line_stripped) and not line_stripped.startswith('PW_'): | ||||||
|                 if stripped_line.startswith("PW_"): |                     # System info | ||||||
|                     parts = stripped_line.split(maxsplit=1) |                     k, v = line_stripped.split('=', 1) | ||||||
|  |                     if k.startswith('NUMA_NODE_'): | ||||||
|  |                         node_id = k[10:] | ||||||
|  |                         self.numa_nodes[node_id] = v | ||||||
|  |                     elif k == 'IS_AMD': | ||||||
|  |                         self.is_amd = v.lower() == 'true' | ||||||
|  |                     elif k == 'LOGICAL_CORE_OPTIONS': | ||||||
|  |                         self.logical_core_options = v.split('!') if v else [] | ||||||
|  |                     elif k == 'LOCALE_LIST': | ||||||
|  |                         self.locale_options = v.split('!') if v else [] | ||||||
|  |                     elif k == 'AMD_VULKAN_DRIVER_LIST': | ||||||
|  |                         self.amd_vulkan_drivers = v.split('!') if v else [] | ||||||
|  |                     continue | ||||||
|  |                 if line_stripped.startswith('PW_'): | ||||||
|  |                     parts = line_stripped.split(maxsplit=1) | ||||||
|                     key = parts[0] |                     key = parts[0] | ||||||
|                     self.available_keys.add(key) |                     self.available_keys.add(key) | ||||||
|                     if len(parts) > 1 and parts[1] == "blocked": |                     if len(parts) > 1 and 'blocked' in parts[1]: | ||||||
|                         self.blocked_keys.add(key) |                         self.blocked_keys.add(key) | ||||||
|  |  | ||||||
|             # Показываем только пересечение |             # Показываем только пересечение | ||||||
|             self.available_keys &= set(self.toggle_settings.keys()) |             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 = QProcess(self) | ||||||
|         process.finished.connect(self.on_show_ppdb_finished) |         process.finished.connect(self.on_show_ppdb_finished) | ||||||
|         process.start(self.start_sh, ["cli", "--show-ppdb", self.ppdb_path]) |         process.start(self.start_sh[0], ["cli", "--show-ppdb", f"{self.exe_path}.ppdb"]) | ||||||
|  |  | ||||||
|     def on_show_ppdb_finished(self, exit_code, exit_status): |     def on_show_ppdb_finished(self, exit_code, exit_status): | ||||||
|         """Handle --show-ppdb output.""" |         """Handle --show-ppdb output.""" | ||||||
|         process = cast(QProcess, self.sender()) |         process = cast(QProcess, self.sender()) | ||||||
|         if exit_code != 0 or exit_status != QProcess.ExitStatus.NormalExit: |         if exit_code != 0 or exit_status != QProcess.ExitStatus.NormalExit: | ||||||
|             logger.warning("Failed to load settings, using defaults") |             # Fallback to defaults if load fails | ||||||
|  |             for key in self.toggle_settings: | ||||||
|  |                 self.current_settings[key] = '0' | ||||||
|  |             for adv_key in ['PW_WINDOWS_VER', 'WINEDLLOVERRIDES', 'LAUNCH_PARAMETERS', | ||||||
|  |                             'PW_WINE_CPU_TOPOLOGY', 'PW_MESA_GL_VERSION_OVERRIDE', | ||||||
|  |                             'PW_VKD3D_FEATURE_LEVEL', 'PW_LOCALE_SELECT', | ||||||
|  |                             'PW_MESA_VK_WSI_PRESENT_MODE', 'PW_AMD_VULKAN_USE', | ||||||
|  |                             'PW_CPU_NUMA_NODE_INDEX']: | ||||||
|  |                 self.current_settings[adv_key] = 'disabled' if 'TOPOLOGY' in adv_key or 'SELECT' in adv_key or 'MODE' in adv_key or 'LEVEL' in adv_key or 'GL_VERSION' in adv_key or 'NUMA' in adv_key else '' | ||||||
|         else: |         else: | ||||||
|             output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore').strip() |             output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore').strip() | ||||||
|             self.current_settings = {} |             self.current_settings = {} | ||||||
|             for line in output.split('\n'): |             for line in output.split('\n'): | ||||||
|                 if '=' in line and line.strip().startswith('PW_'): |                 line_stripped = line.strip() | ||||||
|                     key, val = line.split('=', 1) |                 if '=' in line_stripped: | ||||||
|                     self.current_settings[key.strip()] = val.strip() |                     # Parse all KEY=VALUE lines, not just specific prefixes, to catch more | ||||||
|             logger.debug(f"Loaded current settings: {self.current_settings}") |                     try: | ||||||
|  |                         key, val = line_stripped.split('=', 1) | ||||||
|  |                         if key in self.toggle_settings or key in ['PW_WINDOWS_VER', 'WINEDLLOVERRIDES', 'LAUNCH_PARAMETERS', | ||||||
|  |                                                                  'PW_WINE_CPU_TOPOLOGY', 'PW_MESA_GL_VERSION_OVERRIDE', | ||||||
|  |                                                                  'PW_VKD3D_FEATURE_LEVEL', 'PW_LOCALE_SELECT', | ||||||
|  |                                                                  'PW_MESA_VK_WSI_PRESENT_MODE', 'PW_AMD_VULKAN_USE', | ||||||
|  |                                                                  'PW_CPU_NUMA_NODE_INDEX', 'PW_TASKSET_SLR']: | ||||||
|  |                             self.current_settings[key] = val | ||||||
|  |                     except ValueError: | ||||||
|  |                         continue | ||||||
|  |  | ||||||
|         # Force blocked settings to '0' |         # Force blocked settings to '0' | ||||||
|         for key in self.blocked_keys: |         for key in self.blocked_keys: | ||||||
|             self.current_settings[key] = '0' |             self.current_settings[key] = '0' | ||||||
|  |  | ||||||
|  |         self.original_values = self.current_settings.copy() | ||||||
|  |         for key in set(self.toggle_settings.keys()): | ||||||
|  |             self.original_values.setdefault(key, '0') | ||||||
|  |  | ||||||
|         self.populate_table() |         self.populate_table() | ||||||
|  |         self.populate_advanced() | ||||||
|  |  | ||||||
|     def populate_table(self): |     def populate_table(self): | ||||||
|         """Populate the table with settings that are available in both lists.""" |         """Populate the table with settings that are available in both lists.""" | ||||||
|         self.settings_table.setRowCount(0) |         self.settings_table.setRowCount(0) | ||||||
|         self.value_widgets.clear() |         self.value_widgets.clear() | ||||||
|         self.original_values.clear() |  | ||||||
|         self.settings_table.verticalHeader().setVisible(False) |         self.settings_table.verticalHeader().setVisible(False) | ||||||
|  |  | ||||||
|         visible_keys = sorted(self.available_keys) if self.available_keys else sorted(self.toggle_settings.keys()) |         visible_keys = sorted(self.available_keys) if self.available_keys else sorted(self.toggle_settings.keys()) | ||||||
| @@ -1922,16 +1979,187 @@ class ExeSettingsDialog(QDialog): | |||||||
|  |  | ||||||
|             self.settings_table.setItem(row, 0, name_item) |             self.settings_table.setItem(row, 0, name_item) | ||||||
|             self.value_widgets[(row, 1)] = checkbox |             self.value_widgets[(row, 1)] = checkbox | ||||||
|             self.original_values[toggle] = current_val |  | ||||||
|  |  | ||||||
|         self.settings_table.resizeRowsToContents() |         self.settings_table.resizeRowsToContents() | ||||||
|         if self.settings_table.rowCount() > 0: |         if self.settings_table.rowCount() > 0: | ||||||
|             self.settings_table.setCurrentCell(0, 0) |             self.settings_table.setCurrentCell(0, 0) | ||||||
|             self.settings_table.setFocus(Qt.FocusReason.OtherFocusReason) |             self.settings_table.setFocus(Qt.FocusReason.OtherFocusReason) | ||||||
|  |  | ||||||
|  |     def populate_advanced(self): | ||||||
|  |         """Populate the advanced tab with table format.""" | ||||||
|  |         self.advanced_table.setRowCount(0) | ||||||
|  |         self.advanced_widgets.clear() | ||||||
|  |         self.original_display_values = {} | ||||||
|  |         self.advanced_table.verticalHeader().setVisible(False) | ||||||
|  |  | ||||||
|  |         current = self.current_settings | ||||||
|  |         disabled_text = _('disabled') | ||||||
|  |  | ||||||
|  |         # Define advanced settings configuration | ||||||
|  |         advanced_settings = [] | ||||||
|  |  | ||||||
|  |         # 1. Windows version | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_WINDOWS_VER', | ||||||
|  |             'name': _("Windows version"), | ||||||
|  |             'description': _("Changing the WINDOWS emulation version may be required to run older games. WINDOWS versions below 10 do not support new games with DirectX 12"), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': ['11', '10', '7', 'XP'], | ||||||
|  |             'default': '10' | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 2. Forced to use/disable libraries | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'WINEDLLOVERRIDES', | ||||||
|  |             'name': _("DLL Overrides"), | ||||||
|  |             'description': _("Forced to use/disable the library only for the given application.\n\nA brief instruction:\n* libraries are written WITHOUT the .dll file extension\n* libraries are separated by semicolons - ;\n* library=n - use the WINDOWS (third-party) library\n* library=b - use WINE (built-in) library\n* library=n,b - use WINDOWS library and then WINE\n* library=b,n - use WINE library and then WINDOWS\n* library= - disable the use of this library\n\nExample: libglesv2=;d3dx9_36,d3dx9_42=n,b;mfc120=b,n"), | ||||||
|  |             'type': 'text', | ||||||
|  |             'default': '' | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 3. Launch arguments | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'LAUNCH_PARAMETERS', | ||||||
|  |             'name': _("Launch Arguments"), | ||||||
|  |             'description': _("Adding an argument after the .exe file, just like you would add an argument in a shortcut on a WINDOWS system.\n\nExample: -dx11 -skipintro 1"), | ||||||
|  |             'type': 'text', | ||||||
|  |             'default': '' | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 4. CPU cores limit | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_WINE_CPU_TOPOLOGY', | ||||||
|  |             'name': _("CPU Cores Limit"), | ||||||
|  |             'description': _("Limiting the number of CPU cores is useful for Unity games (It is recommended to set the value equal to 8)"), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': [disabled_text] + self.logical_core_options, | ||||||
|  |             'default': disabled_text | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 5. OpenGL version | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_MESA_GL_VERSION_OVERRIDE', | ||||||
|  |             'name': _("OpenGL Version"), | ||||||
|  |             'description': _("You can select the required OpenGL version, some games require a forced Compatibility Profile (COMP)."), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': [disabled_text, '4.6COMPAT', '4.5COMPAT', '4.3COMPAT', '4.1COMPAT', '3.3COMPAT', '3.2COMPAT'], | ||||||
|  |             'default': disabled_text | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 6. VKD3D feature level | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_VKD3D_FEATURE_LEVEL', | ||||||
|  |             'name': _("VKD3D Feature Level"), | ||||||
|  |             'description': _("You can set a forced feature level VKD3D for games on DirectX12"), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': [disabled_text, '12_2', '12_1', '12_0', '11_1', '11_0'], | ||||||
|  |             'default': disabled_text | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 7. Locale | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_LOCALE_SELECT', | ||||||
|  |             'name': _("Locale"), | ||||||
|  |             'description': _("Force certain locale for an app. Fixes encoding issues in legacy software"), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': [disabled_text] + self.locale_options, | ||||||
|  |             'default': disabled_text | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 8. Present mode | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_MESA_VK_WSI_PRESENT_MODE', | ||||||
|  |             'name': _("Window Mode"), | ||||||
|  |             'description': _("Window mode (for Vulkan and OpenGL):\nfifo - First in, first out. Limits the frame rate + no tearing. (VSync)\nimmediate - Unlimited frame rate + tearing.\nmailbox - Triple buffering. Unlimited frame rate + no tearing.\nrelaxed - Same as fifo but allows tearing when below the monitors refresh rate."), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': [disabled_text, 'fifo', 'immediate', 'mailbox', 'relaxed'], | ||||||
|  |             'default': disabled_text | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 9. AMD Vulkan (always show, block if not applicable) | ||||||
|  |         amd_options = [disabled_text] + self.amd_vulkan_drivers if self.is_amd and self.amd_vulkan_drivers else [disabled_text] | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_AMD_VULKAN_USE', | ||||||
|  |             'name': _("AMD Vulkan Driver"), | ||||||
|  |             'description': _("Select needed AMD vulkan implementation. Choosing which implementation of vulkan will be used to run the game"), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': amd_options, | ||||||
|  |             'default': disabled_text | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # 10. NUMA node (always show if numa_nodes exist, block if <=1) | ||||||
|  |         numa_ids = sorted(self.numa_nodes.keys()) | ||||||
|  |         numa_options = [disabled_text] + numa_ids if len(numa_ids) > 1 else [disabled_text] | ||||||
|  |         advanced_settings.append({ | ||||||
|  |             'key': 'PW_CPU_NUMA_NODE_INDEX', | ||||||
|  |             'name': _("NUMA Node"), | ||||||
|  |             'description': _("NUMA node for CPU affinity. In multi-core systems, CPUs are split into NUMA nodes, each with its own local memory and cores. Binding a game to a single node reduces memory-access latency and limits costly core-to-core switches."), | ||||||
|  |             'type': 'combo', | ||||||
|  |             'options': numa_options, | ||||||
|  |             'default': disabled_text | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         # Populate table | ||||||
|  |         for setting in advanced_settings: | ||||||
|  |             row = self.advanced_table.rowCount() | ||||||
|  |             self.advanced_table.insertRow(row) | ||||||
|  |  | ||||||
|  |             # Name column | ||||||
|  |             name_item = QTableWidgetItem(setting['name']) | ||||||
|  |             name_item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled) | ||||||
|  |             self.advanced_table.setItem(row, 0, name_item) | ||||||
|  |  | ||||||
|  |             # Value column (widget) | ||||||
|  |             if setting['type'] == 'combo': | ||||||
|  |                 combo = QComboBox() | ||||||
|  |                 combo.addItems(setting['options']) | ||||||
|  |  | ||||||
|  |                 # Get current value | ||||||
|  |                 current_raw = current.get(setting['key'], setting['default']) | ||||||
|  |                 if setting['key'] == 'PW_WINE_CPU_TOPOLOGY': | ||||||
|  |                     current_val = disabled_text if current_raw == 'disabled' else (current_raw.split(':')[0] if isinstance(current_raw, str) and ':' in current_raw else current_raw) | ||||||
|  |                 elif setting['key'] == 'PW_AMD_VULKAN_USE': | ||||||
|  |                     current_val = disabled_text if not current_raw or current_raw == '' else current_raw | ||||||
|  |                 else: | ||||||
|  |                     current_val = disabled_text if current_raw == 'disabled' else current_raw | ||||||
|  |  | ||||||
|  |                 if current_val not in setting['options']: | ||||||
|  |                     combo.addItem(current_val) | ||||||
|  |                 combo.setCurrentText(current_val) | ||||||
|  |  | ||||||
|  |                 # Block if only disabled option | ||||||
|  |                 if len(setting['options']) == 1: | ||||||
|  |                     combo.setEnabled(False) | ||||||
|  |  | ||||||
|  |                 self.advanced_table.setCellWidget(row, 1, combo) | ||||||
|  |                 self.advanced_widgets[setting['key']] = combo | ||||||
|  |                 self.original_display_values[setting['key']] = current_val | ||||||
|  |  | ||||||
|  |             elif setting['type'] == 'text': | ||||||
|  |                 text_edit = QTextEdit() | ||||||
|  |                 current_val = current.get(setting['key'], setting['default']) | ||||||
|  |                 text_edit.setPlainText(current_val) | ||||||
|  |  | ||||||
|  |                 self.advanced_table.setCellWidget(row, 1, text_edit) | ||||||
|  |                 self.advanced_widgets[setting['key']] = text_edit | ||||||
|  |                 self.original_display_values[setting['key']] = current_val | ||||||
|  |  | ||||||
|  |             # Description column | ||||||
|  |             desc_item = QTableWidgetItem(setting['description']) | ||||||
|  |             desc_item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled) | ||||||
|  |             desc_item.setToolTip(setting['description']) | ||||||
|  |             desc_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) | ||||||
|  |             self.advanced_table.setItem(row, 2, desc_item) | ||||||
|  |  | ||||||
|  |         self.advanced_table.resizeRowsToContents() | ||||||
|  |         if self.advanced_table.rowCount() > 0: | ||||||
|  |             self.advanced_table.setCurrentCell(0, 0) | ||||||
|  |  | ||||||
|     def apply_changes(self): |     def apply_changes(self): | ||||||
|         """Apply changes by collecting diffs and running --edit-db.""" |         """Apply changes by collecting diffs from both main and advanced tabs.""" | ||||||
|         changes = [] |         changes = [] | ||||||
|  |  | ||||||
|  |         # --- 1. Обычные (toggle) настройки --- | ||||||
|         for key, orig_val in self.original_values.items(): |         for key, orig_val in self.original_values.items(): | ||||||
|             if key in self.blocked_keys: |             if key in self.blocked_keys: | ||||||
|                 continue  # Skip blocked keys |                 continue  # Skip blocked keys | ||||||
| @@ -1952,14 +2180,32 @@ class ExeSettingsDialog(QDialog): | |||||||
|             if new_val != orig_val: |             if new_val != orig_val: | ||||||
|                 changes.append(f"{key}={new_val}") |                 changes.append(f"{key}={new_val}") | ||||||
|  |  | ||||||
|  |         # --- 2. Advanced настройки --- | ||||||
|  |         for key, widget in self.advanced_widgets.items(): | ||||||
|  |             orig_val = self.original_display_values.get(key, '') | ||||||
|  |             if isinstance(widget, QComboBox): | ||||||
|  |                 new_val = widget.currentText() | ||||||
|  |                 # приведение disabled к 'disabled' | ||||||
|  |                 if new_val.lower() == _('disabled').lower(): | ||||||
|  |                     new_val = 'disabled' | ||||||
|  |             elif isinstance(widget, QTextEdit): | ||||||
|  |                 new_val = widget.toPlainText().strip() | ||||||
|  |             else: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             if new_val != orig_val: | ||||||
|  |                 changes.append(f"{key}={new_val}") | ||||||
|  |  | ||||||
|  |         # --- 3. Проверка на изменения --- | ||||||
|         if not changes: |         if not changes: | ||||||
|             QMessageBox.information(self, _("Info"), _("No changes to apply.")) |             QMessageBox.information(self, _("Info"), _("No changes to apply.")) | ||||||
|             return |             return | ||||||
|  |  | ||||||
|  |         # --- 4. Запуск процесса сохранения --- | ||||||
|         process = QProcess(self) |         process = QProcess(self) | ||||||
|         process.finished.connect(self.on_edit_db_finished) |         process.finished.connect(self.on_edit_db_finished) | ||||||
|         args = ["cli", "--edit-db", self.exe_path] + changes |         args = ["cli", "--edit-db", self.exe_path] + changes | ||||||
|         process.start(self.start_sh, args) |         process.start(self.start_sh[0], args) | ||||||
|         self.apply_button.setEnabled(False) |         self.apply_button.setEnabled(False) | ||||||
|  |  | ||||||
|     def on_edit_db_finished(self, exit_code, exit_status): |     def on_edit_db_finished(self, exit_code, exit_status): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user