From 5571f74125ef0a93f734557f1db841c95bbfd1b3 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Mon, 8 Sep 2025 12:43:11 +0600 Subject: [PATCH 01/11] the order of tabs in the Wine/Proton version selection window has been changed --- winehelper_gui.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 13a955c..f4621ba 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1134,8 +1134,10 @@ class WineVersionSelectionDialog(QDialog): self._create_version_tab("Системный", [(self.system_wine_display_name, "system")]) - # --- Other versions from JSON --- - group_keys = sorted(self.wine_versions_data.keys()) + # Определяем желаемый порядок вкладок + tab_order = ["WINE", "WINE_LG", "PROTON_LG", "PROTON_STEAM"] + # Сортируем ключи в соответствии с заданным порядком + group_keys = sorted(self.wine_versions_data.keys(), key=lambda k: tab_order.index(k) if k in tab_order else len(tab_order)) for key in group_keys: versions = self.wine_versions_data.get(key, []) From 3e91bcf241ffe5674aa795ab5ac93122f3e45275 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Mon, 8 Sep 2025 14:10:45 +0600 Subject: [PATCH 02/11] added the status of the stop app button --- winehelper_gui.py | 122 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 13 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index f4621ba..d2c6d99 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1275,6 +1275,7 @@ class WineHelperGUI(QMainWindow): self.current_active_button = None self.installed_buttons = [] self.install_tabs_data = {} + self.running_apps = {} # {desktop_path: QProcess} self.current_selected_app = None self.icon_animators = {} self.previous_tab_index = 0 @@ -1483,7 +1484,7 @@ class WineHelperGUI(QMainWindow): # --- Верхний ряд кнопок --- top_buttons_layout = QHBoxLayout() self.run_button = QPushButton("Запустить") - self.run_button.clicked.connect(self.run_installed_app) + self.run_button.clicked.connect(self.toggle_run_stop_app) top_buttons_layout.addWidget(self.run_button) installed_action_layout.addLayout(top_buttons_layout) @@ -2704,6 +2705,12 @@ class WineHelperGUI(QMainWindow): self.current_selected_app['name'] = name self.current_selected_app['exec'] = exec_cmd + # Состояния кнопки + if desktop_path in self.running_apps: + self.run_button.setText("Остановить") + else: + self.run_button.setText("Запустить") + # Показываем панель информации self.info_panel.setVisible(True) @@ -2853,6 +2860,11 @@ class WineHelperGUI(QMainWindow): def run_installed_app_with_debug(self): """Запускает выбранное установленное приложение с созданием лога""" + if self.current_selected_app and self.current_selected_app.get('desktop_path') in self.running_apps: + QMessageBox.information(self, "Приложение запущено", + "Приложение уже запущено. Остановите его, прежде чем запускать с отладкой.") + return + # Создаем кастомные кнопки yes_button = QPushButton("Да") no_button = QPushButton("Нет") @@ -2973,23 +2985,81 @@ class WineHelperGUI(QMainWindow): QMessageBox.critical(self, "Ошибка запуска", f"Не удалось запустить команду:\n{' '.join(command)}\n\nОшибка: {str(e)}") - def run_installed_app(self): - """Запускает выбранное установленное приложение""" - self._run_app_launcher(debug=False) + def toggle_run_stop_app(self): + """Запускает или останавливает выбранное приложение.""" + if not self.current_selected_app or 'desktop_path' not in self.current_selected_app: + QMessageBox.warning(self, "Ошибка", "Сначала выберите приложение.") + return + + desktop_path = self.current_selected_app['desktop_path'] + + # Если приложение запущено, останавливаем его + if desktop_path in self.running_apps: + process = self.running_apps.get(desktop_path) + if process and process.state() != QProcess.NotRunning: + prefix_name = self._get_prefix_name_for_selected_app() + if not prefix_name: + QMessageBox.warning(self, "Ошибка", "Не удалось определить префикс для остановки приложения.\n" + "Попробуйте закрыть приложение вручную.") + # Fallback to killing the wrapper script, though it might not work + process.terminate() + if not process.waitForFinished(1000): + process.kill() + return + + prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + wine_executable = self._get_wine_executable_for_prefix(prefix_name) + + wineserver_path = os.path.join(os.path.dirname(wine_executable), "wineserver") + if not shutil.which(wineserver_path): + wineserver_path = "wineserver" + + kill_proc = QProcess(self) + kill_env = QProcessEnvironment.systemEnvironment() + kill_env.insert("WINEPREFIX", prefix_path) + kill_proc.setProcessEnvironment(kill_env) + + print(f"Остановка приложений в префиксе '{prefix_name}'...") + kill_proc.start(wineserver_path, ["-k"]) + kill_proc.waitForFinished(5000) # Даем до 5 секунд на выполнение. + else: + # Состояние не совпадает, убираем из словаря + print(f"Процесс для {desktop_path} уже не запущен, очистка.") + self._on_app_process_finished(desktop_path) + + # Если приложение не запущено, запускаем его + else: + # Запускаем без отладки. Кнопка отладки отдельная. + self._run_app_launcher(debug=False) + + def _on_app_process_finished(self, desktop_path): + """Обрабатывает завершение процесса запущенного приложения.""" + if desktop_path in self.running_apps: + process = self.running_apps.pop(desktop_path) + process.deleteLater() # Clean up the QProcess object + print(f"Процесс для {desktop_path} завершен.") + + # Если текущее выбранное приложение - то, что только что завершилось, обновляем кнопку + if self.current_selected_app and self.current_selected_app.get('desktop_path') == desktop_path: + self.run_button.setText("Запустить") + else: + print(f"Предупреждение: получен сигнал finished для неизвестного процесса {desktop_path}") def _run_app_launcher(self, debug=False): - """Внутренний метод для запуска приложения (с отладкой или без)""" + """Внутренний метод для запуска приложения (с отладкой или без) с использованием QProcess.""" if not self.current_selected_app or 'exec' not in self.current_selected_app: QMessageBox.warning(self, "Ошибка", "Сначала выберите приложение.") return + desktop_path = self.current_selected_app['desktop_path'] command_str = self.current_selected_app['exec'] - try: - # Используем shlex для безопасного разбора командной строки - command_parts = shlex.split(command_str) + if desktop_path in self.running_apps: + print(f"Приложение {self.current_selected_app.get('name')} уже запущено.") + return - # Удаляем параметры (%F и подобные) + try: + command_parts = shlex.split(command_str) clean_command = [part for part in command_parts if not part.startswith('%')] if debug: @@ -3011,13 +3081,39 @@ class WineHelperGUI(QMainWindow): QMessageBox.critical(self, "Ошибка", f"Не удалось модифицировать команду для отладки: {e}") return - # Удаление префикса + process = QProcess(self) + env = QProcessEnvironment.systemEnvironment() + + cmd_start_index = 0 + if clean_command and clean_command[0] == 'env': + cmd_start_index = 1 + while cmd_start_index < len(clean_command) and '=' in clean_command[cmd_start_index]: + key, value = clean_command[cmd_start_index].split('=', 1) + env.insert(key, value.strip('"\'')) + cmd_start_index += 1 + + if cmd_start_index >= len(clean_command): + raise ValueError("Не найдена команда для выполнения в строке Exec.") + + program = clean_command[cmd_start_index] + arguments = clean_command[cmd_start_index + 1:] + + process.setProcessEnvironment(env) + process.finished.connect(lambda: self._on_app_process_finished(desktop_path)) + try: - subprocess.Popen(clean_command) - print(f"Запущено: {' '.join(clean_command)}") + process.start(program, arguments) + if not process.waitForStarted(3000): + raise RuntimeError(f"Не удалось запустить процесс: {process.errorString()}") + + self.running_apps[desktop_path] = process + self.run_button.setText("Остановить") + print(f"Запущено: {program} {' '.join(arguments)}") except Exception as e: QMessageBox.critical(self, "Ошибка запуска", - f"Не удалось запустить команду:\n{' '.join(clean_command)}\n\nОшибка: {str(e)}") + f"Не удалось запустить команду:\n{command_str}\n\nОшибка: {str(e)}") + if desktop_path in self.running_apps: + del self.running_apps[desktop_path] except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось обработать команду запуска:\n{command_str}\n\nОшибка: {str(e)}") From aa267ad9efcb76edccd6812abf90ae94a5fea85f Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Mon, 8 Sep 2025 15:00:18 +0600 Subject: [PATCH 03/11] expanded information output in the prefix information window --- winehelper_gui.py | 223 +++++++++++++++++++++++++++------------------- 1 file changed, 131 insertions(+), 92 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index d2c6d99..09a0a4a 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1280,16 +1280,9 @@ class WineHelperGUI(QMainWindow): self.icon_animators = {} self.previous_tab_index = 0 self.selected_wine_version_value = None - self.created_prefixes_info = {} # Хранит информацию о префиксах, созданных на вкладке self.current_managed_prefix_name = None # Имя префикса, выбранного в выпадающем списке - self.pending_prefix_info = None # Временное хранилище информации о создаваемом префиксе + self.prefixes_before_install = set() - # Добавим путь к файлу состояния - self.config_dir = os.path.join(os.path.expanduser("~"), ".config", "winehelper") - os.makedirs(self.config_dir, exist_ok=True) - self.state_file = os.path.join(self.config_dir, "gui_state.json") - - # State for command dialog log processing (specifically for prefix creation) self.command_output_buffer = "" self.command_last_line_was_progress = False # Создаем главный виджет и layout @@ -1325,7 +1318,7 @@ class WineHelperGUI(QMainWindow): self.create_help_tab() # Загружаем состояние после создания всех виджетов - self._load_state() + self._load_created_prefixes() # Инициализируем состояние, которое будет использоваться для логов self._reset_log_state() @@ -2016,69 +2009,61 @@ class WineHelperGUI(QMainWindow): self.wine_version_edit.textChanged.connect(self.update_create_prefix_button_state) self.prefix_install_path_edit.textChanged.connect(self.update_prefix_install_button_state) - def _remove_prefix_from_gui_state(self, prefix_name): - """Удаляет префикс из внутреннего состояния и пользовательского интерфейса вкладки 'Создать префикс'.""" - if prefix_name in self.created_prefixes_info: - del self.created_prefixes_info[prefix_name] + def _get_current_prefixes(self): + """Возвращает множество имен существующих префиксов.""" + prefixes_root_path = os.path.join(Var.USER_WORK_PATH, "prefixes") + if not os.path.isdir(prefixes_root_path): + return set() + try: + return { + name for name in os.listdir(prefixes_root_path) + if os.path.isdir(os.path.join(prefixes_root_path, name)) + } + except OSError as e: + print(f"Предупреждение: не удалось прочитать директорию префиксов: {e}") + return set() + def _remove_prefix_from_gui_state(self, prefix_name): + """Удаляет префикс из пользовательского интерфейса вкладки 'Создать префикс'.""" index_to_remove = self.created_prefix_selector.findText(prefix_name) if index_to_remove != -1: self.created_prefix_selector.removeItem(index_to_remove) - # Сохраняем состояние после удаления. on_created_prefix_selected также вызовет сохранение, - # но этот вызов гарантирует сохранение, даже если сигналы были заблокированы. - self._save_state() - - def _load_state(self): - """Загружает последнее состояние GUI из файла.""" - if not os.path.exists(self.state_file): + def _load_created_prefixes(self): + """Загружает список созданных префиксов, сканируя файловую систему, и восстанавливает последнее выбранное состояние.""" + prefixes_root_path = os.path.join(Var.USER_WORK_PATH, "prefixes") + if not os.path.isdir(prefixes_root_path): + self.management_container_groupbox.setVisible(False) return + try: - with open(self.state_file, 'r', encoding='utf-8') as f: - state = json.load(f) + prefix_names = sorted([ + name for name in os.listdir(prefixes_root_path) + if os.path.isdir(os.path.join(prefixes_root_path, name)) + ]) + except OSError as e: + print(f"Предупреждение: не удалось прочитать директорию префиксов: {e}") + prefix_names = [] - loaded_prefixes = state.get('created_prefixes_info', {}) - if not loaded_prefixes: - return + self.created_prefix_selector.blockSignals(True) + self.created_prefix_selector.clear() + if prefix_names: + self.created_prefix_selector.addItems(prefix_names) + self.created_prefix_selector.blockSignals(False) - # Блокируем сигналы, чтобы избежать преждевременного срабатывания - self.created_prefix_selector.blockSignals(True) - self.created_prefix_selector.clear() - self.created_prefixes_info.clear() + if not prefix_names: + self.management_container_groupbox.setVisible(False) + self.on_created_prefix_selected(-1) # Убедимся, что панель управления сброшена + return - for prefix_name, prefix_info in loaded_prefixes.items(): - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) - if os.path.isdir(prefix_path): - self.created_prefixes_info[prefix_name] = prefix_info - self.created_prefix_selector.addItem(prefix_name) + self.management_container_groupbox.setVisible(True) - self.created_prefix_selector.blockSignals(False) - - if self.created_prefix_selector.count() > 0: - self.management_container_groupbox.setVisible(True) - last_managed = state.get('current_managed_prefix_name') - index = self.created_prefix_selector.findText(last_managed) - if index != -1: - self.created_prefix_selector.setCurrentIndex(index) - else: - self.created_prefix_selector.setCurrentIndex(0) # Выбираем первый, если предыдущий не найден - else: - self.management_container_groupbox.setVisible(False) - - except (IOError, json.JSONDecodeError, TypeError) as e: - print(f"Предупреждение: не удалось загрузить состояние GUI: {e}") - - def _save_state(self): - """Сохраняет текущее состояние GUI в файл.""" - state = { - 'created_prefixes_info': self.created_prefixes_info, - 'current_managed_prefix_name': self.current_managed_prefix_name - } - try: - with open(self.state_file, 'w', encoding='utf-8') as f: - json.dump(state, f, indent=2, ensure_ascii=False) - except IOError as e: - print(f"Предупреждение: не удалось сохранить состояние GUI: {e}") + # По умолчанию выбираем первый элемент в списке, если он есть. + if self.created_prefix_selector.count() > 0: + self.created_prefix_selector.setCurrentIndex(0) + else: + # Если список пуст, убедимся, что панель управления сброшена. + self.on_created_prefix_selected(-1) def on_created_prefix_selected(self, index): """Обрабатывает выбор префикса из выпадающего списка.""" @@ -2091,7 +2076,6 @@ class WineHelperGUI(QMainWindow): self.current_managed_prefix_name = prefix_name self._setup_prefix_management_panel(prefix_name) self.delete_prefix_button.setEnabled(True) - self._save_state() def delete_selected_prefix(self): """Удаляет префикс, выбранный в выпадающем списке на вкладке 'Создать префикс'.""" @@ -2165,31 +2149,76 @@ class WineHelperGUI(QMainWindow): def _setup_prefix_management_panel(self, prefix_name): """Настраивает панель управления префиксом на основе текущего состояния.""" - if prefix_name and prefix_name in self.created_prefixes_info: - self.prefix_management_groupbox.setEnabled(True) + is_prefix_selected = bool(prefix_name) + self.prefix_management_groupbox.setEnabled(is_prefix_selected) + self.create_launcher_button.setEnabled(is_prefix_selected) + + if is_prefix_selected: self.update_prefix_info_display(prefix_name) else: - self.prefix_management_groupbox.setEnabled(False) self.prefix_info_display.clear() self.prefix_install_path_edit.clear() - # Кнопка "Создать ярлык" должна быть активна, если выбран действительный префикс - is_prefix_selected = bool(prefix_name and prefix_name in self.created_prefixes_info) - self.create_launcher_button.setEnabled(is_prefix_selected) + self.update_prefix_install_button_state() def update_prefix_info_display(self, prefix_name): - """Обновляет информационный блок для созданного префикса.""" - info = self.created_prefixes_info.get(prefix_name) - if not info: + """Обновляет информационный блок для созданного префикса, читая данные из last.conf.""" + if not prefix_name: self.prefix_info_display.clear() return - html_content = f""" -

- Имя: {html.escape(info['name'])}
- Архитектура: {html.escape(info['arch'])}
- Версия Wine: {html.escape(info['wine_version'])}
- Путь: {html.escape(info['path'])}

""" + last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf") + + if not os.path.exists(last_conf_path): + self.prefix_info_display.setHtml(f"

Файл конфигурации last.conf не найден для префикса '{prefix_name}'.

") + return + + # Словарь для хранения всех переменных из файла + all_vars = {} + try: + with open(last_conf_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line.startswith('export '): + parts = line[7:].split('=', 1) + if len(parts) == 2: + key = parts[0].strip() + value = parts[1].strip().strip('"\'') + all_vars[key] = value + except IOError as e: + self.prefix_info_display.setHtml(f"

Ошибка чтения last.conf: {e}

") + return + + # Карта для красивого отображения известных переменных + display_map = { + "WINEPREFIX": ("Путь", lambda v: v), + "WINEARCH": ("Архитектура", lambda v: "64-bit" if v == "win64" else "32-bit"), + "WH_WINE_USE": ("Версия Wine", lambda v: "Системная" if v == "system" else v), + "BASE_PFX": ("Тип", lambda v: 'Чистый' if v == "none" else 'С рекомендуемыми библиотеками'), + } + display_order = ["WINEPREFIX", "WINEARCH", "WH_WINE_USE", "BASE_PFX"] + + html_content = f'

' + html_content += f"Имя: {html.escape(prefix_name)}
" + + # Отображаем известные переменные в заданном порядке + for key in display_order: + if key in all_vars: + label, formatter = display_map[key] + value = formatter(all_vars[key]) + html_content += f"{html.escape(label)}: {html.escape(value)}
" + + # Отображаем остальные (неизвестные) переменные + other_vars_html = "" + for key, value in sorted(all_vars.items()): + if key not in display_map: + other_vars_html += f"  {html.escape(key)}: {html.escape(value)}
" + + if other_vars_html: + html_content += "
Дополнительные параметры:
" + html_content += other_vars_html + + html_content += "

" self.prefix_info_display.setHtml(html_content) def browse_for_prefix_installer(self): @@ -2484,15 +2513,6 @@ class WineHelperGUI(QMainWindow): wine_use = self.selected_wine_version_value wine_use_display = self.wine_version_edit.text() - # Сохраняем информацию для отображения после создания - self.pending_prefix_info = { - 'name': prefix_name, - 'path': prefix_path, - 'arch': "32-bit" if wine_arch == "win32" else "64-bit", - 'type': 'Чистый' if base_pfx else 'С рекомендуемыми библиотеками', - 'wine_version': wine_use_display - } - self.command_dialog = QDialog(self) self.command_dialog.setWindowTitle(f"Создание префикса: {prefix_name}") self.command_dialog.setMinimumSize(750, 400) @@ -2556,10 +2576,6 @@ class WineHelperGUI(QMainWindow): self._handle_command_finished(exit_code, exit_status) if exit_code == 0: # Добавляем новый префикс в список и выбираем его - if self.pending_prefix_info: - self.created_prefixes_info[prefix_name] = self.pending_prefix_info - self.pending_prefix_info = None - if self.created_prefix_selector.findText(prefix_name) == -1: self.created_prefix_selector.addItem(prefix_name) @@ -2568,7 +2584,6 @@ class WineHelperGUI(QMainWindow): if not self.management_container_groupbox.isVisible(): self.management_container_groupbox.setVisible(True) - self._save_state() self.prefix_name_edit.clear() self.wine_version_edit.clear() QMessageBox.information(self, "Успех", @@ -3403,6 +3418,8 @@ class WineHelperGUI(QMainWindow): if not self._show_license_agreement_dialog(): return # Пользователь отклонил лицензию + self.prefixes_before_install = self._get_current_prefixes() + self.installation_cancelled = False # Создаем диалоговое окно установки @@ -3641,11 +3658,33 @@ class WineHelperGUI(QMainWindow): if exit_code == 0 and exit_status == QProcess.NormalExit: self.append_log("\n=== Установка успешно завершена ===") + + # --- Обновление списка префиксов --- + # Определяем, какой префикс был создан + prefixes_after_install = self._get_current_prefixes() + new_prefixes = prefixes_after_install - getattr(self, 'prefixes_before_install', set()) + + # Перезагружаем список префиксов на вкладке "Создать префикс" + self._load_created_prefixes() + + new_prefix_name = None + if new_prefixes: + # Обычно создается один префикс, берем первый из найденных + new_prefix_name = new_prefixes.pop() + # Находим и выбираем его в выпадающем списке + index = self.created_prefix_selector.findText(new_prefix_name) + if index != -1: + self.created_prefix_selector.setCurrentIndex(index) + # --- Конец обновления списка префиксов --- + # Создаем кастомный диалог, чтобы кнопка была на русском success_box = QMessageBox(self.install_dialog) success_box.setWindowTitle("Успех") title_name = self._get_current_app_title() - success_box.setText(f"Программа «{title_name}» установлена успешно!") + success_text = f"Программа «{title_name}» установлена успешно!" + if new_prefix_name: + success_text += f"\n\nНовый префикс '{new_prefix_name}' был автоматически выбран в списке управления на вкладке 'Создать префикс'." + success_box.setText(success_text) success_box.setIcon(QMessageBox.Information) success_box.addButton("Готово", QMessageBox.AcceptRole) success_box.exec_() From 861b6743fd5c4153c1ad1e7f508b20b7f5d02aa5 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Mon, 8 Sep 2025 15:09:29 +0600 Subject: [PATCH 04/11] the Create Prefix tab has been renamed to the Prefix Manager tab --- winehelper_gui.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 09a0a4a..8dd0324 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1406,7 +1406,7 @@ class WineHelperGUI(QMainWindow): self.info_panel_layout.setStretch(1, 1) self.info_panel_layout.setStretch(4, 0) - if current_tab_text in ["Справка", "Создать префикс"]: + if current_tab_text in ["Справка", "Менеджер префиксов"]: self.info_panel.setVisible(False) else: self.info_panel.setVisible(True) @@ -1821,7 +1821,7 @@ class WineHelperGUI(QMainWindow): self.selected_wine_version_value = None def create_prefix_tab(self): - """Создает вкладку для создания нового префикса""" + """Создает вкладку 'Менеджер префиксов'""" self.prefix_tab = QWidget() layout = QVBoxLayout(self.prefix_tab) layout.setContentsMargins(10, 10, 10, 10) @@ -2001,7 +2001,7 @@ class WineHelperGUI(QMainWindow): container_layout.addWidget(self.prefix_management_groupbox) layout.addWidget(self.management_container_groupbox) layout.addStretch() - self.add_tab(self.prefix_tab, "Создать префикс") + self.add_tab(self.prefix_tab, "Менеджер префиксов") self.arch_win32_radio.toggled.connect(self.clear_wine_version_selection) self.prefix_name_edit.textChanged.connect(self.update_create_prefix_button_state) @@ -2024,7 +2024,7 @@ class WineHelperGUI(QMainWindow): return set() def _remove_prefix_from_gui_state(self, prefix_name): - """Удаляет префикс из пользовательского интерфейса вкладки 'Создать префикс'.""" + """Удаляет префикс из пользовательского интерфейса вкладки 'Менеджер префиксов'.""" index_to_remove = self.created_prefix_selector.findText(prefix_name) if index_to_remove != -1: self.created_prefix_selector.removeItem(index_to_remove) @@ -2078,7 +2078,7 @@ class WineHelperGUI(QMainWindow): self.delete_prefix_button.setEnabled(True) def delete_selected_prefix(self): - """Удаляет префикс, выбранный в выпадающем списке на вкладке 'Создать префикс'.""" + """Удаляет префикс, выбранный в выпадающем списке на вкладке 'Менеджер префиксов'.""" prefix_name = self.current_managed_prefix_name if not prefix_name: return @@ -3664,7 +3664,7 @@ class WineHelperGUI(QMainWindow): prefixes_after_install = self._get_current_prefixes() new_prefixes = prefixes_after_install - getattr(self, 'prefixes_before_install', set()) - # Перезагружаем список префиксов на вкладке "Создать префикс" + # Перезагружаем список префиксов на вкладке "Менеджер префиксов" self._load_created_prefixes() new_prefix_name = None @@ -3683,7 +3683,7 @@ class WineHelperGUI(QMainWindow): title_name = self._get_current_app_title() success_text = f"Программа «{title_name}» установлена успешно!" if new_prefix_name: - success_text += f"\n\nНовый префикс '{new_prefix_name}' был автоматически выбран в списке управления на вкладке 'Создать префикс'." + success_text += f"\n\nНовый префикс '{new_prefix_name}' был автоматически выбран в списке управления на вкладке 'Менеджер префиксов'." success_box.setText(success_text) success_box.setIcon(QMessageBox.Information) success_box.addButton("Готово", QMessageBox.AcceptRole) From 3f22b3540ef53f863219cbe9bd3648c06216981e Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Mon, 8 Sep 2025 15:53:32 +0600 Subject: [PATCH 05/11] prefix creation is displayed in a separate window --- winehelper_gui.py | 265 +++++++++++++++++++++++++++------------------- 1 file changed, 158 insertions(+), 107 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 8dd0324..8766a95 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1227,6 +1227,133 @@ class WineVersionSelectionDialog(QDialog): self.selected_display_text = version_name self.accept() +class CreatePrefixDialog(QDialog): + """Диалог для создания нового префикса.""" + + def __init__(self, parent=None): + super().__init__(parent) + self.parent_gui = parent # Store reference to main window + self.setWindowTitle("Создание нового префикса") + self.setMinimumSize(500, 250) + self.setModal(True) + + # Attributes to store results + self.prefix_name = None + self.wine_arch = None + self.base_pfx = None + self.selected_wine_version_value = None + self.selected_wine_version_display = None + + layout = QVBoxLayout(self) + form_layout = QFormLayout() + form_layout.setSpacing(10) + + self.prefix_name_edit = QLineEdit() + self.prefix_name_edit.setPlaceholderText("Например: my_prefix") + form_layout.addRow("Имя нового префикса:", self.prefix_name_edit) + + arch_widget = QWidget() + arch_layout = QHBoxLayout(arch_widget) + arch_layout.setContentsMargins(0, 0, 0, 0) + self.arch_win32_radio = QRadioButton("32-bit") + self.arch_win64_radio = QRadioButton("64-bit") + self.arch_win64_radio.setChecked(True) + arch_layout.addWidget(self.arch_win32_radio) + arch_layout.addWidget(self.arch_win64_radio) + form_layout.addRow("Разрядность:", arch_widget) + + type_widget = QWidget() + type_layout = QHBoxLayout(type_widget) + type_layout.setContentsMargins(0, 0, 0, 0) + self.type_clean_radio = QRadioButton("Чистый") + self.type_clean_radio.setToolTip("Создает пустой префикс Wine без каких-либо дополнительных компонентов.") + self.type_recommended_radio = QRadioButton("С рекомендуемыми библиотеками") + tooltip_text = "Устанавливает базовый набор компонентов, необходимый для большинства приложений" + self.type_recommended_radio.setToolTip(tooltip_text) + self.type_clean_radio.setChecked(True) + type_layout.addWidget(self.type_clean_radio) + type_layout.addWidget(self.type_recommended_radio) + form_layout.addRow("Наполнение:", type_widget) + + self.wine_version_edit = QLineEdit() + self.wine_version_edit.setReadOnly(True) + self.wine_version_edit.setPlaceholderText("Версия не выбрана") + + select_version_button = QPushButton("Выбрать версию...") + select_version_button.clicked.connect(self.open_wine_version_dialog) + + version_layout = QHBoxLayout() + version_layout.addWidget(self.wine_version_edit) + version_layout.addWidget(select_version_button) + form_layout.addRow("Версия Wine/Proton:", version_layout) + + layout.addLayout(form_layout) + + # Buttons + button_layout = QHBoxLayout() + self.create_button = QPushButton("Создать") + self.create_button.setFont(QFont('Arial', 11, QFont.Bold)) + self.create_button.setStyleSheet("background-color: #0078d7; color: white;") + self.create_button.setEnabled(False) + self.create_button.clicked.connect(self.accept_creation) + + cancel_button = QPushButton("Отмена") + cancel_button.clicked.connect(self.reject) + + button_layout.addStretch() + button_layout.addWidget(self.create_button) + button_layout.addWidget(cancel_button) + layout.addLayout(button_layout) + + # Connect signals + self.arch_win32_radio.toggled.connect(self.clear_wine_version_selection) + self.prefix_name_edit.textChanged.connect(self.update_create_button_state) + self.wine_version_edit.textChanged.connect(self.update_create_button_state) + + def open_wine_version_dialog(self): + """Открывает диалог выбора версии Wine.""" + architecture = "win32" if self.arch_win32_radio.isChecked() else "win64" + dialog = WineVersionSelectionDialog(architecture, self) + if dialog.exec_() == QDialog.Accepted and dialog.selected_version: + self.wine_version_edit.setText(dialog.selected_display_text) + self.selected_wine_version_value = dialog.selected_version + + def clear_wine_version_selection(self): + """Сбрасывает выбор версии Wine.""" + self.wine_version_edit.clear() + self.selected_wine_version_value = None + + def update_create_button_state(self): + """Включает или выключает кнопку 'Создать'.""" + name_ok = bool(self.prefix_name_edit.text().strip()) + version_ok = bool(self.wine_version_edit.text().strip()) + self.create_button.setEnabled(name_ok and version_ok) + + def accept_creation(self): + """Валидирует данные, сохраняет их и закрывает диалог с успехом.""" + prefix_name = self.prefix_name_edit.text().strip() + + if not prefix_name: + QMessageBox.warning(self, "Ошибка", "Имя префикса не может быть пустым.") + return + + if not re.match(r'^[a-zA-Z0-9_.-]+$', prefix_name): + QMessageBox.warning(self, "Ошибка", "Имя префикса может содержать только латинские буквы, цифры, точки, дефисы и подчеркивания.") + return + + prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + if os.path.exists(prefix_path): + QMessageBox.warning(self, "Ошибка", f"Префикс с именем '{prefix_name}' уже существует.") + return + + # Save data + self.prefix_name = prefix_name + self.wine_arch = "win32" if self.arch_win32_radio.isChecked() else "win64" + self.base_pfx = "none" if self.type_clean_radio.isChecked() else "" + self.selected_wine_version_display = self.wine_version_edit.text() + + self.accept() + class WineHelperGUI(QMainWindow): def __init__(self): super().__init__() @@ -1279,7 +1406,6 @@ class WineHelperGUI(QMainWindow): self.current_selected_app = None self.icon_animators = {} self.previous_tab_index = 0 - self.selected_wine_version_value = None self.current_managed_prefix_name = None # Имя префикса, выбранного в выпадающем списке self.prefixes_before_install = set() @@ -1804,81 +1930,25 @@ class WineHelperGUI(QMainWindow): ) self.add_tab(installed_tab, "Установленные") - def open_wine_version_dialog(self): - """Открывает диалог выбора версии Wine.""" - architecture = "win32" if self.arch_win32_radio.isChecked() else "win64" - dialog = WineVersionSelectionDialog(architecture, self) - if dialog.exec_() == QDialog.Accepted and dialog.selected_version: - self.wine_version_edit.setText(dialog.selected_display_text) - self.selected_wine_version_value = dialog.selected_version - - def clear_wine_version_selection(self): - """ - Сбрасывает выбор версии Wine при смене архитектуры, - чтобы заставить пользователя выбрать заново. - """ - self.wine_version_edit.clear() - self.selected_wine_version_value = None - def create_prefix_tab(self): """Создает вкладку 'Менеджер префиксов'""" self.prefix_tab = QWidget() layout = QVBoxLayout(self.prefix_tab) layout.setContentsMargins(10, 10, 10, 10) + layout.setSpacing(10) - form_layout = QFormLayout() - form_layout.setSpacing(10) - - self.prefix_name_edit = QLineEdit() - self.prefix_name_edit.setPlaceholderText("Например: my_prefix") - form_layout.addRow("Имя нового префикса:", self.prefix_name_edit) - - arch_widget = QWidget() - arch_layout = QHBoxLayout(arch_widget) - arch_layout.setContentsMargins(0, 0, 0, 0) - self.arch_win32_radio = QRadioButton("32-bit") - self.arch_win64_radio = QRadioButton("64-bit") - self.arch_win64_radio.setChecked(True) - arch_layout.addWidget(self.arch_win32_radio) - arch_layout.addWidget(self.arch_win64_radio) - form_layout.addRow("Разрядность:", arch_widget) - - type_widget = QWidget() - type_layout = QHBoxLayout(type_widget) - type_layout.setContentsMargins(0, 0, 0, 0) - self.type_clean_radio = QRadioButton("Чистый") - self.type_clean_radio.setToolTip("Создает пустой префикс Wine без каких-либо дополнительных компонентов.") - self.type_recommended_radio = QRadioButton("С рекомендуемыми библиотеками") - tooltip_text = "Устанавливает базовый набор компонентов, необходимый для большинства приложений" - self.type_recommended_radio.setToolTip(tooltip_text) - self.type_clean_radio.setChecked(True) - type_layout.addWidget(self.type_clean_radio) - type_layout.addWidget(self.type_recommended_radio) - form_layout.addRow("Наполнение:", type_widget) - - self.wine_version_edit = QLineEdit() - self.wine_version_edit.setReadOnly(True) - self.wine_version_edit.setPlaceholderText("Версия не выбрана") - - select_version_button = QPushButton("Выбрать версию...") - select_version_button.clicked.connect(self.open_wine_version_dialog) - - version_layout = QHBoxLayout() - version_layout.addWidget(self.wine_version_edit) - version_layout.addWidget(select_version_button) - form_layout.addRow("Версия Wine/Proton:", version_layout) - - self.create_prefix_button = QPushButton("Создать префикс") - self.create_prefix_button.setFont(QFont('Arial', 12, QFont.Bold)) - self.create_prefix_button.setStyleSheet("background-color: #0078d7; color: white;") - self.create_prefix_button.setEnabled(False) - self.create_prefix_button.clicked.connect(self.start_prefix_creation) - - layout.addLayout(form_layout) - layout.addWidget(self.create_prefix_button) + # --- Контейнер для создания нового префикса --- + creation_groupbox = QGroupBox() + creation_layout = QVBoxLayout(creation_groupbox) + create_prefix_button = QPushButton("Создать новый префикс") + create_prefix_button.setFont(QFont('Arial', 12, QFont.Bold)) + create_prefix_button.setStyleSheet("background-color: #0078d7; color: white; padding: 5px;") + create_prefix_button.clicked.connect(self.open_create_prefix_dialog) + creation_layout.addWidget(create_prefix_button) + layout.addWidget(creation_groupbox) # --- Контейнер для выбора и управления созданными префиксами --- - self.management_container_groupbox = QGroupBox("Управление созданными префиксами") + self.management_container_groupbox = QGroupBox() self.management_container_groupbox.setVisible(False) # Скрыт, пока нет префиксов container_layout = QVBoxLayout(self.management_container_groupbox) @@ -2003,10 +2073,6 @@ class WineHelperGUI(QMainWindow): layout.addStretch() self.add_tab(self.prefix_tab, "Менеджер префиксов") - self.arch_win32_radio.toggled.connect(self.clear_wine_version_selection) - self.prefix_name_edit.textChanged.connect(self.update_create_prefix_button_state) - self.prefix_name_edit.textChanged.connect(self.on_prefix_name_edited) - self.wine_version_edit.textChanged.connect(self.update_create_prefix_button_state) self.prefix_install_path_edit.textChanged.connect(self.update_prefix_install_button_state) def _get_current_prefixes(self): @@ -2072,6 +2138,10 @@ class WineHelperGUI(QMainWindow): self._setup_prefix_management_panel(None) self.delete_prefix_button.setEnabled(False) else: + # Прокручиваем к выбранному элементу, чтобы он был виден в списке + self.created_prefix_selector.view().scrollTo( + self.created_prefix_selector.model().index(index, 0) + ) prefix_name = self.created_prefix_selector.itemText(index) self.current_managed_prefix_name = prefix_name self._setup_prefix_management_panel(prefix_name) @@ -2141,12 +2211,6 @@ class WineHelperGUI(QMainWindow): else: QMessageBox.critical(self, "Ошибка удаления", f"Не удалось удалить префикс '{prefix_name}'.\nПодробности смотрите в логе.") - def on_prefix_name_edited(self, text): - """Сбрасывает состояние управления префиксом, когда пользователь вводит новое имя.""" - if text: - if self.created_prefix_selector.currentIndex() != -1: - self.created_prefix_selector.setCurrentIndex(-1) - def _setup_prefix_management_panel(self, prefix_name): """Настраивает панель управления префиксом на основе текущего состояния.""" is_prefix_selected = bool(prefix_name) @@ -2478,40 +2542,29 @@ class WineHelperGUI(QMainWindow): self.add_tab(help_tab, "Справка") - def update_create_prefix_button_state(self): - """Включает или выключает кнопку 'Создать префикс' в зависимости от заполнения полей.""" - name_ok = bool(self.prefix_name_edit.text().strip()) - version_ok = bool(self.wine_version_edit.text().strip()) - self.create_prefix_button.setEnabled(name_ok and version_ok) + def open_create_prefix_dialog(self): + """Открывает диалог создания нового префикса.""" + dialog = CreatePrefixDialog(self) + if dialog.exec_() == QDialog.Accepted: + if not self._show_license_agreement_dialog(): + return - def start_prefix_creation(self): - """Запускает создание префикса после валидации.""" - if not self._show_license_agreement_dialog(): - return + self.start_prefix_creation( + prefix_name=dialog.prefix_name, + wine_arch=dialog.wine_arch, + base_pfx=dialog.base_pfx, + wine_use=dialog.selected_wine_version_value, + wine_use_display=dialog.selected_wine_version_display + ) + + def start_prefix_creation(self, prefix_name, wine_arch, base_pfx, wine_use, wine_use_display): + """Запускает создание префикса с заданными параметрами.""" # Сбрасываем выбор в выпадающем списке, чтобы панель управления скрылась на время создания if self.created_prefix_selector.count() > 0: self.created_prefix_selector.setCurrentIndex(-1) - prefix_name = self.prefix_name_edit.text().strip() - - if not prefix_name: - QMessageBox.warning(self, "Ошибка", "Имя префикса не может быть пустым.") - return - - if not re.match(r'^[a-zA-Z0-9_.-]+$', prefix_name): - QMessageBox.warning(self, "Ошибка", "Имя префикса может содержать только латинские буквы, цифры, точки, дефисы и подчеркивания.") - return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) - if os.path.exists(prefix_path): - QMessageBox.warning(self, "Ошибка", f"Префикс с именем '{prefix_name}' уже существует.") - return - - wine_arch = "win32" if self.arch_win32_radio.isChecked() else "win64" - base_pfx = "none" if self.type_clean_radio.isChecked() else "" - wine_use = self.selected_wine_version_value - wine_use_display = self.wine_version_edit.text() self.command_dialog = QDialog(self) self.command_dialog.setWindowTitle(f"Создание префикса: {prefix_name}") @@ -2584,8 +2637,6 @@ class WineHelperGUI(QMainWindow): if not self.management_container_groupbox.isVisible(): self.management_container_groupbox.setVisible(True) - self.prefix_name_edit.clear() - self.wine_version_edit.clear() QMessageBox.information(self, "Успех", f"Префикс '{prefix_name}' успешно создан.\n" "Теперь вы можете управлять им, выбрав его из выпадающего списка.") From a57df9a259777060c163fe3c7c99d37ae3e4e633 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Mon, 8 Sep 2025 16:02:46 +0600 Subject: [PATCH 06/11] prefix control buttons have been removed from the Installed tab --- winehelper_gui.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 8766a95..401d0b5 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1607,45 +1607,6 @@ class WineHelperGUI(QMainWindow): top_buttons_layout.addWidget(self.run_button) installed_action_layout.addLayout(top_buttons_layout) - # --- Сетка с утилитами --- - utils_grid_layout = QGridLayout() - utils_grid_layout.setSpacing(5) - - # Ряд 0 - self.winetricks_button = QPushButton("Менеджер компонентов") - self.winetricks_button.clicked.connect(self.open_winetricks_manager) - self.winetricks_button.setToolTip("Установка компонентов, библиотек и шрифтов в префикс с помощью Winetricks.") - utils_grid_layout.addWidget(self.winetricks_button, 0, 0) - - self.winecfg_button = QPushButton("Редактор настроек") - self.winecfg_button.clicked.connect(lambda: self._run_wine_util('winecfg')) - self.winecfg_button.setToolTip("Запуск утилиты winecfg для настройки параметров Wine (версия Windows, диски, аудио и т.д.).") - utils_grid_layout.addWidget(self.winecfg_button, 0, 1) - - # Ряд 1 - self.regedit_button = QPushButton("Редактор реестра") - self.regedit_button.clicked.connect(lambda: self._run_wine_util('regedit')) - self.regedit_button.setToolTip("Запуск редактора реестра Wine (regedit) для просмотра и изменения ключей реестра в префиксе.") - utils_grid_layout.addWidget(self.regedit_button, 1, 0) - - self.uninstaller_button = QPushButton("Удаление программ") - self.uninstaller_button.clicked.connect(lambda: self._run_wine_util('uninstaller')) - self.uninstaller_button.setToolTip("Запуск стандартного деинсталлятора Wine для удаления установленных в префикс Windows-программ.") - utils_grid_layout.addWidget(self.uninstaller_button, 1, 1) - - # Ряд 2 - self.cmd_button = QPushButton("Командная строка") - self.cmd_button.clicked.connect(lambda: self._run_wine_util('cmd')) - self.cmd_button.setToolTip("Запуск командной строки (cmd) в окружении выбранного префикса.") - utils_grid_layout.addWidget(self.cmd_button, 2, 0) - - self.winefile_button = QPushButton("Файловый менеджер") - self.winefile_button.clicked.connect(lambda: self._run_wine_util('winefile')) - self.winefile_button.setToolTip("Запуск файлового менеджера Wine (winefile) для просмотра файлов внутри префикса.") - utils_grid_layout.addWidget(self.winefile_button, 2, 1) - - installed_action_layout.addLayout(utils_grid_layout) - self.installed_action_widget.setLayout(installed_action_layout) self.info_panel_layout.addWidget(self.installed_action_widget) From 1e19fa3c56011d9d77dea33705b646e43224c2b7 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Mon, 8 Sep 2025 21:17:45 +0600 Subject: [PATCH 07/11] added control buttons for dxvk/vkd3d --- winehelper | 86 ++++++++++++--- winehelper_gui.py | 264 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 331 insertions(+), 19 deletions(-) diff --git a/winehelper b/winehelper index a48f3be..0b91476 100755 --- a/winehelper +++ b/winehelper @@ -776,9 +776,10 @@ init_wined3d () { init_dxvk () { check_variables USE_DXVK_VER "$1" - get_dxvk () { - DXVK_URL="$1" - DXVK_PACKAGE="${WH_VULKAN_LIBDIR}/dxvk-${DXVK_VAR_VER}.tar.$(echo ${DXVK_URL#*.tar.})" + get_dxvk() { + local DXVK_URL="$1" + local DXVK_VAR_VER="$2" + local DXVK_PACKAGE="${WH_VULKAN_LIBDIR}/${DXVK_VAR_VER}.tar.$(echo "${DXVK_URL#*.tar.}")" if try_download "$DXVK_URL" "$DXVK_PACKAGE" check256sum \ && unpack "$DXVK_PACKAGE" "$WH_VULKAN_LIBDIR" then @@ -789,8 +790,8 @@ init_dxvk () { } for DXVK_VAR_VER in "$USE_DXVK_VER" $@ ; do - if [[ ! -d "${WH_VULKAN_LIBDIR}/dxvk-$DXVK_VAR_VER" ]] ; then - get_dxvk "$CLOUD_URL/dxvk-${DXVK_VAR_VER}.tar.xz" + if [[ ! -d "${WH_VULKAN_LIBDIR}/${DXVK_VAR_VER}" ]] ; then + get_dxvk "$CLOUD_URL/${DXVK_VAR_VER}.tar.xz" "$DXVK_VAR_VER" fi done @@ -803,8 +804,8 @@ init_dxvk () { fi for dxvkfiles in $DXVK_FILES ; do - try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/dxvk-$USE_DXVK_VER/x64/$dxvkfiles.dll" - if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/dxvk-$USE_DXVK_VER/x32/$dxvkfiles.dll" + try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/${USE_DXVK_VER}/x64/$dxvkfiles.dll" + if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/${USE_DXVK_VER}/x32/$dxvkfiles.dll" then var_winedlloverride_update "$dxvkfiles=n" fi done @@ -813,9 +814,10 @@ init_dxvk () { init_vkd3d () { check_variables USE_VKD3D_VER "$1" - get_vkd3d () { - VKD3D_URL="$1" - VKD3D_PACKAGE="${WH_VULKAN_LIBDIR}/vkd3d-proton-${VKD3D_VAR_VER}.tar.$(echo ${VKD3D_URL#*.tar.})" + get_vkd3d() { + local VKD3D_URL="$1" + local VKD3D_VAR_VER="$2" + local VKD3D_PACKAGE="${WH_VULKAN_LIBDIR}/${VKD3D_VAR_VER}.tar.$(echo "${VKD3D_URL#*.tar.}")" if try_download "$VKD3D_URL" "$VKD3D_PACKAGE" check256sum \ && unpack "$VKD3D_PACKAGE" "$WH_VULKAN_LIBDIR" then @@ -826,15 +828,15 @@ init_vkd3d () { } for VKD3D_VAR_VER in "$USE_VKD3D_VER" $@ ; do - if [[ ! -d "${WH_VULKAN_LIBDIR}/vkd3d-proton-$VKD3D_VAR_VER" ]] ; then - get_vkd3d "$CLOUD_URL/vkd3d-proton-${VKD3D_VAR_VER}.tar.xz" + if [[ ! -d "${WH_VULKAN_LIBDIR}/${VKD3D_VAR_VER}" ]] ; then + get_vkd3d "$CLOUD_URL/${VKD3D_VAR_VER}.tar.xz" "$VKD3D_VAR_VER" fi done VKD3D_FILES="d3d12 d3d12core libvkd3d-shader-1 libvkd3d-1" # libvkd3d-proton-utils-3 for vkd3dfiles in $VKD3D_FILES ; do - try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/vkd3d-proton-$USE_VKD3D_VER/x64/$vkd3dfiles.dll" - if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/vkd3d-proton-$USE_VKD3D_VER/x86/$vkd3dfiles.dll" + try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/${USE_VKD3D_VER}/x64/$vkd3dfiles.dll" + if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/${USE_VKD3D_VER}/x86/$vkd3dfiles.dll" then var_winedlloverride_update "$vkd3dfiles=n" fi done @@ -2011,6 +2013,57 @@ restore_prefix() { return 0 } +update_last_conf_var() { + local var_name="$1" + local new_value="$2" + local conf_file="$WINEPREFIX/last.conf" + + if [[ ! -f "$conf_file" ]]; then + print_warning "Файл last.conf не найден, не могу обновить переменную $var_name." + return 1 + fi + + if grep -q "export $var_name=" "$conf_file"; then + sed -i "s|^export $var_name=.*|export $var_name=\"$new_value\"|" "$conf_file" + else + echo "export $var_name=\"$new_value\"" >> "$conf_file" + fi +} + +run_install_dxvk() { + local version="$1" + check_prefix_var + init_database + init_wine_ver + init_wineprefix + if [[ "$version" == "none" ]] ; then + print_info "Удаление DXVK..." + init_wined3d + update_last_conf_var "DXVK_VER" "" + else + init_dxvk "$version" + update_last_conf_var "DXVK_VER" "$USE_DXVK_VER" + fi + wait_wineserver +} + +run_install_vkd3d() { + local version="$1" + check_prefix_var + init_database + init_wine_ver + init_wineprefix + if [[ "$version" == "none" ]] ; then + print_info "Удаление VKD3D..." + init_wined3d + update_last_conf_var "VKD3D_VER" "" + else + init_vkd3d "$version" + update_last_conf_var "VKD3D_VER" "$USE_VKD3D_VER" + fi + wait_wineserver +} + wh_info () { echo "Использование: $SCRIPT_NAME [команда] @@ -2019,6 +2072,9 @@ wh_info () { install [скрипт] запустить скрипт установки программы install [скрипт] --clear-pfx не использовать готовый префикс для установки ПО + install-dxvk [версия|none] установить/удалить DXVK в выбранный префикс + install-vkd3d [версия|none] установить/удалить VKD3D в выбранный префикс + installed список установленных программ run [программа] запуск программы (отладка) remove-all удалить WineHelper и все связанные данные @@ -2066,6 +2122,8 @@ case "$arg1" in winetricks) prepair_wine ; "$WH_WINETRICKS" -q "$@" ;; desktop) create_desktop "$@" ; exit 0 ;; install|-i) run_autoinstall "$@" ;; + install-dxvk) run_install_dxvk "$@" ;; + install-vkd3d) run_install_vkd3d "$@" ;; installed) check_installed_programs "$1" ;; run|-r) run_installed_programs "$1" ;; backup-prefix) backup_prefix "$@" ;; diff --git a/winehelper_gui.py b/winehelper_gui.py index 401d0b5..1897e5e 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1078,7 +1078,7 @@ class WineVersionSelectionDialog(QDialog): if not line: continue - match = re.match(r'^#+\s+([A-Z_]+)\s+#*$', line) + match = re.match(r'^#+\s*([^#]+?)\s*#*$', line) if match: group_name = match.group(1) allowed_groups = {"WINE", "WINE_LG", "PROTON_LG", "PROTON_STEAM"} @@ -1354,6 +1354,118 @@ class CreatePrefixDialog(QDialog): self.accept() +class ComponentVersionSelectionDialog(QDialog): + """Диалог для выбора версии компонента (DXVK, VKD3D).""" + + def __init__(self, component_group, title, parent=None, add_extra_options=True): + super().__init__(parent) + self.component_group = component_group + self.selected_version = None + self.versions_data = [] + + self.setWindowTitle(title) + self.setMinimumSize(600, 400) + self.setModal(True) + + main_layout = QVBoxLayout(self) + + self.search_edit = QLineEdit() + self.search_edit.setPlaceholderText("Поиск версии...") + self.search_edit.textChanged.connect(self.filter_versions) + main_layout.addWidget(self.search_edit) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + main_layout.addWidget(self.scroll_area) + + scroll_content = QWidget() + self.scroll_area.setWidget(scroll_content) + + self.grid_layout = QGridLayout(scroll_content) + self.grid_layout.setAlignment(Qt.AlignTop) + + self.buttons = [] + # Кнопка "Удалить" теперь находится вне сетки, поэтому начинаем с 0 строки. + self.load_versions(start_row=0) + + # --- Панель с кнопками действий внизу диалога --- + button_layout = QHBoxLayout() + + if add_extra_options: + uninstall_btn = QPushButton("Удалить из префикса") + uninstall_btn.setToolTip("Удаляет текущую установленную версию компонента из префикса.") + uninstall_btn.clicked.connect(partial(self.on_version_selected, "none")) + # Добавляем кнопку слева + button_layout.addWidget(uninstall_btn) + + button_layout.addStretch(1) # Растягиваем пространство, чтобы разнести кнопки + + cancel_button = QPushButton("Отмена") + cancel_button.clicked.connect(self.reject) + button_layout.addWidget(cancel_button) + + main_layout.addLayout(button_layout) + + def load_versions(self, start_row): + """Загружает и отображает версии.""" + self._parse_sha256_list() + self.populate_ui(start_row) + + def _parse_sha256_list(self): + """Парсит sha256sum.list для получения списка версий.""" + sha256_path = os.path.join(Var.DATA_PATH, "sha256sum.list") + if not os.path.exists(sha256_path): + self.versions_data = [] + return + + current_group = None + try: + with open(sha256_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line: + continue + match = re.match(r'^#+\s*([^#]+?)\s*#*$', line) + if match: + current_group = match.group(1).strip() + continue + if current_group == self.component_group and re.match(r'^[a-f0-9]{64}', line): + parts = line.split(maxsplit=1) + if len(parts) == 2: + filename = parts[1] + if filename.endswith('.tar.xz'): + version_name = filename[:-7] + self.versions_data.append(version_name) + except IOError: + self.versions_data = [] + + def populate_ui(self, start_row): + """Заполняет UI кнопками версий.""" + versions = sorted(self.versions_data, reverse=True) + num_columns = 3 + row, col = start_row, 0 + for version_name in versions: + btn = QPushButton(version_name) + btn.clicked.connect(partial(self.on_version_selected, version_name)) + self.grid_layout.addWidget(btn, row, col) + self.buttons.append(btn) + col += 1 + if col >= num_columns: + col = 0 + row += 1 + + def filter_versions(self): + """Фильтрует видимость кнопок версий на основе текста поиска.""" + search_text = self.search_edit.text().lower() + for btn_widget in self.buttons: + is_match = search_text in btn_widget.text().lower() + btn_widget.setVisible(is_match) + + def on_version_selected(self, version_name): + """Обрабатывает выбор версии.""" + self.selected_version = version_name + self.accept() + class WineHelperGUI(QMainWindow): def __init__(self): super().__init__() @@ -1980,11 +2092,29 @@ class WineHelperGUI(QMainWindow): self.prefix_winefile_button.setToolTip("Запуск файлового менеджера Wine (winefile) для просмотра файлов внутри префикса.") management_layout.addWidget(self.prefix_winefile_button, 2, 1) + # Добавляем небольшой отступ + spacer_widget = QWidget() + spacer_widget.setFixedHeight(5) + management_layout.addWidget(spacer_widget, 3, 0, 1, 2) + + self.dxvk_manage_button = QPushButton("Управление DXVK") + self.dxvk_manage_button.setMinimumHeight(32) + self.dxvk_manage_button.clicked.connect(lambda: self.open_component_version_manager('dxvk')) + self.dxvk_manage_button.setToolTip("Установка или удаление определенной версии DXVK в префиксе.") + management_layout.addWidget(self.dxvk_manage_button, 4, 0) + + self.vkd3d_manage_button = QPushButton("Управление VKD3D") + self.vkd3d_manage_button.setMinimumHeight(32) + self.vkd3d_manage_button.clicked.connect(lambda: self.open_component_version_manager('vkd3d-proton')) + self.vkd3d_manage_button.setToolTip("Установка или удаление определенной версии vkd3d-proton в префиксе.") + management_layout.addWidget(self.vkd3d_manage_button, 4, 1) + # --- Правая сторона: Информационный блок --- self.prefix_info_display = QTextBrowser() self.prefix_info_display.setReadOnly(True) self.prefix_info_display.setFrameStyle(QFrame.StyledPanel) - management_layout.addWidget(self.prefix_info_display, 0, 2, 3, 1) + # Увеличиваем rowspan, чтобы учесть добавленный отступ + management_layout.addWidget(self.prefix_info_display, 0, 2, 5, 1) management_layout.setColumnStretch(0, 1) management_layout.setColumnStretch(1, 1) @@ -1994,7 +2124,7 @@ class WineHelperGUI(QMainWindow): separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setFrameShadow(QFrame.Sunken) - management_layout.addWidget(separator, 3, 0, 1, 3) + management_layout.addWidget(separator, 5, 0, 1, 3) install_group = QWidget() install_layout = QVBoxLayout(install_group) @@ -2027,7 +2157,7 @@ class WineHelperGUI(QMainWindow): action_buttons_layout.addWidget(self.create_launcher_button) install_layout.addLayout(action_buttons_layout) - management_layout.addWidget(install_group, 4, 0, 1, 3) + management_layout.addWidget(install_group, 6, 0, 1, 3) container_layout.addWidget(self.prefix_management_groupbox) layout.addWidget(self.management_container_groupbox) @@ -2220,8 +2350,10 @@ class WineHelperGUI(QMainWindow): "WINEARCH": ("Архитектура", lambda v: "64-bit" if v == "win64" else "32-bit"), "WH_WINE_USE": ("Версия Wine", lambda v: "Системная" if v == "system" else v), "BASE_PFX": ("Тип", lambda v: 'Чистый' if v == "none" else 'С рекомендуемыми библиотеками'), + "DXVK_VER": ("Версия DXVK", lambda v: v if v else "Не установлено"), + "VKD3D_VER": ("Версия VKD3D", lambda v: v if v else "Не установлено"), } - display_order = ["WINEPREFIX", "WINEARCH", "WH_WINE_USE", "BASE_PFX"] + display_order = ["WINEPREFIX", "WINEARCH", "WH_WINE_USE", "BASE_PFX", "DXVK_VER", "VKD3D_VER"] html_content = f'

' html_content += f"Имя: {html.escape(prefix_name)}
" @@ -2310,6 +2442,128 @@ class WineHelperGUI(QMainWindow): self.command_process.start(wine_executable, args) self.command_dialog.exec_() + def _get_prefix_component_version(self, prefix_name, component_key): + """ + Читает last.conf префикса и возвращает версию указанного компонента. + :param prefix_name: Имя префикса. + :param component_key: Ключ компонента (например, 'DXVK_VER'). + :return: Строку с версией или None, если не найдено или значение пустое. + """ + if not prefix_name: + return None + + last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf") + if not os.path.exists(last_conf_path): + return None + + try: + with open(last_conf_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + # Ищем строку вида 'export KEY="value"' или 'export KEY=value' + if line.startswith('export '): + parts = line[7:].split('=', 1) + if len(parts) == 2: + key = parts[0].strip() + if key == component_key: + value = parts[1].strip().strip('"\'') + # Возвращаем значение, только если оно не пустое. + return value if value else None + except IOError as e: + print(f"Ошибка чтения last.conf для {prefix_name}: {e}") + return None + return None + + def open_component_version_manager(self, component): + """Открывает диалог выбора версии для DXVK/VKD3D и запускает установку.""" + prefix_name = self.current_managed_prefix_name + if not prefix_name: + QMessageBox.warning(self, "Ошибка", "Сначала выберите префикс.") + return + + component_key = None + if component == 'dxvk': + group = 'DXVK' + title = f"Управление DXVK для префикса: {prefix_name}" + command = 'install-dxvk' + component_key = 'DXVK_VER' + elif component == 'vkd3d-proton': + group = 'VKD3D' + title = f"Управление vkd3d для префикса: {prefix_name}" + command = 'install-vkd3d' + component_key = 'VKD3D_VER' + else: + return + + dialog = ComponentVersionSelectionDialog(group, title, self) + if dialog.exec_() == QDialog.Accepted and dialog.selected_version: + version = dialog.selected_version + + if version == "none": + # Удаление: сначала проверяем, есть ли что удалять. + installed_version = self._get_prefix_component_version(prefix_name, component_key) + if not installed_version: + QMessageBox.information(self, "Информация", "Установленных версий нет.") + return # Прерываем выполнение, т.к. удалять нечего + # Для удаления лицензия не нужна, запускаем сразу. + self.run_component_install_command(prefix_name, command, version) + else: + # Установка: сначала показываем лицензионное соглашение. + if not self._show_license_agreement_dialog(): + return # Пользователь отклонил лицензию + + # Если лицензия принята, запускаем установку. + self.run_component_install_command(prefix_name, command, version) + + def run_component_install_command(self, prefix_name, command, version): + """Выполняет команду установки компонента (DXVK/VKD3D) через winehelper.""" + prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + + self.command_dialog = QDialog(self) + self.command_dialog.setWindowTitle(f"Выполнение: {command} {version}") + self.command_dialog.setMinimumSize(750, 400) + self.command_dialog.setModal(True) + self.command_dialog.setWindowFlags(self.command_dialog.windowFlags() & ~Qt.WindowCloseButtonHint) + + layout = QVBoxLayout() + self.command_log_output = QTextEdit() + self.command_log_output.setReadOnly(True) + self.command_log_output.setFont(QFont('DejaVu Sans Mono', 10)) + layout.addWidget(self.command_log_output) + + self.command_close_button = QPushButton("Закрыть") + self.command_close_button.setEnabled(False) + self.command_close_button.clicked.connect(self.command_dialog.close) + layout.addWidget(self.command_close_button) + self.command_dialog.setLayout(layout) + + self.command_process = QProcess(self.command_dialog) + self.command_process.setProcessChannelMode(QProcess.MergedChannels) + self.command_process.readyReadStandardOutput.connect(self._handle_command_output) + self.command_process.finished.connect( + lambda exit_code, exit_status: self._handle_component_install_finished( + prefix_name, exit_code, exit_status + ) + ) + + env = QProcessEnvironment.systemEnvironment() + env.insert("WINEPREFIX", prefix_path) + self.command_process.setProcessEnvironment(env) + + args = [command, version] + self.command_log_output.append(f"Выполнение: {shlex.quote(self.winehelper_path)} {' '.join(shlex.quote(a) for a in args)}") + self.command_process.start(self.winehelper_path, args) + self.command_dialog.exec_() + + def _handle_component_install_finished(self, prefix_name, exit_code, exit_status): + """Обрабатывает завершение установки компонента и обновляет информацию о префиксе.""" + # Вызываем общий обработчик для обновления лога и кнопки закрытия + self._handle_command_finished(exit_code, exit_status) + + # В случае успеха обновляем панель информации о префиксе + if exit_code == 0: + self.update_prefix_info_display(prefix_name) + def create_launcher_for_prefix(self): """ Открывает диалог для создания ярлыка для приложения внутри выбранного префикса. From 7f3f330fc4bc798bd40adde0dfe80370bc52e990 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Tue, 9 Sep 2025 10:45:09 +0600 Subject: [PATCH 08/11] improved result display when searching for dxvk/vkd3d --- winehelper_gui.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 1897e5e..a5639e9 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1455,11 +1455,24 @@ class ComponentVersionSelectionDialog(QDialog): row += 1 def filter_versions(self): - """Фильтрует видимость кнопок версий на основе текста поиска.""" + """Фильтрует видимость кнопок версий и перестраивает сетку для плотного отображения.""" search_text = self.search_edit.text().lower() + + visible_buttons = [] for btn_widget in self.buttons: - is_match = search_text in btn_widget.text().lower() - btn_widget.setVisible(is_match) + if search_text in btn_widget.text().lower(): + visible_buttons.append(btn_widget) + + # Сначала скроем все кнопки, чтобы очистить сетку + for btn_widget in self.buttons: + btn_widget.setVisible(False) + + # Затем добавим только видимые кнопки, перестраивая сетку + num_columns = 3 # Как определено в populate_ui + for i, btn_widget in enumerate(visible_buttons): + row, col = divmod(i, num_columns) + self.grid_layout.addWidget(btn_widget, row, col) + btn_widget.setVisible(True) def on_version_selected(self, version_name): """Обрабатывает выбор версии.""" From 721fd5e76e670f33ab35b906e71a10dc86881cd8 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Tue, 9 Sep 2025 12:55:11 +0600 Subject: [PATCH 09/11] added the installation of dxvk/vkd3d in the text interface --- winehelper | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/winehelper b/winehelper index 0b91476..d4fdee4 100755 --- a/winehelper +++ b/winehelper @@ -2018,7 +2018,7 @@ update_last_conf_var() { local new_value="$2" local conf_file="$WINEPREFIX/last.conf" - if [[ ! -f "$conf_file" ]]; then + if [[ ! -f "$conf_file" ]] ; then print_warning "Файл last.conf не найден, не могу обновить переменную $var_name." return 1 fi @@ -2030,8 +2030,108 @@ update_last_conf_var() { fi } +list_component_versions() { + local component_group="$1" + local sha256_file="$DATA_PATH/sha256sum.list" + [[ ! -f "$sha256_file" ]] && fatal "Файл с версиями не найден: $sha256_file" + + print_info "Доступные версии для $component_group:" + + awk -v group="$component_group" ' + /^#+\s*([^#]+?)\s*#*$/ { + current_group = $0 + gsub(/^#+\s*|\s*#*$/, "", current_group) + } + /^[a-f0-9]{64}/ { + if (current_group == group) { + filename = $2 + sub(/\.tar\.xz$/, "", filename) + print " - " filename + } + } + ' "$sha256_file" | sort -Vr +} + +select_component_version() { + local component_group="$1" + local sha256_file="$DATA_PATH/sha256sum.list" + [[ ! -f "$sha256_file" ]] && fatal "Файл с версиями не найден: $sha256_file" + + local versions=() + local current_group="" + while IFS= read -r line; do + if [[ "$line" =~ ^#+[[:space:]]([^#[:space:]]+)[[:space:]]#* ]] ; then + current_group="${BASH_REMATCH[1]}" + elif [[ "$current_group" == "$component_group" ]] && [[ "$line" =~ [a-f0-9]{64} ]] ; then + local filename + filename=$(echo "$line" | awk '{print $2}') + local version_name=${filename%.tar.xz} + versions+=("$version_name") + fi + done < "$sha256_file" + + IFS=$'\n' versions=($(sort -Vr <<<"${versions[*]}")) + unset IFS + + if [[ ${#versions[@]} -eq 0 ]] ; then + print_warning "Не найдено доступных версий для $component_group." >&2 + return 1 + fi + + print_info "Выберите версию $component_group для установки:" >&2 + echo >&2 + + local items_to_print=(" 0) Отмена") + for i in "${!versions[@]}" ; do + items_to_print+=(" $((i+1))) ${versions[$i]}") + done + + local num_items=${#items_to_print[@]} + local term_width=${COLUMNS:-80} + local max_len=0 + for item in "${items_to_print[@]}" ; do + (( ${#item} > max_len )) && max_len=${#item} + done + + ((max_len+=2)) + local num_cols=$(( term_width / max_len )) + (( num_cols = num_cols > 0 ? num_cols : 1 )) + local num_rows=$(( (num_items + num_cols - 1) / num_cols )) + + for ((i=0; i&2 + done + echo >&2 + done + + local max_choice=${#versions[@]} + local user_choice + while true; do + echo >&2 + read -p "Введите номер (0-$max_choice): " user_choice + if [[ "$user_choice" =~ ^[0-9]+$ ]] && (( user_choice >= 0 && user_choice <= max_choice )) ; then + if [[ "$user_choice" == "0" ]] ; then + return 1 + fi + echo "${versions[$((user_choice-1))]}" + return 0 + else + print_error "Неверный выбор. Введите число от 0 до $max_choice." >&2 + fi + done +} + run_install_dxvk() { local version="$1" + if [[ -z "$version" ]] ; then + version=$(select_component_version "DXVK") + [[ $? -ne 0 ]] && print_info "Установка DXVK отменена." && return + elif [[ "$version" == "list" ]]; then + list_component_versions "DXVK" + return + fi check_prefix_var init_database init_wine_ver @@ -2049,6 +2149,13 @@ run_install_dxvk() { run_install_vkd3d() { local version="$1" + if [[ -z "$version" ]] ; then + version=$(select_component_version "VKD3D") + [[ $? -ne 0 ]] && print_info "Установка VKD3D отменена." && return + elif [[ "$version" == "list" ]] ; then + list_component_versions "VKD3D" + return + fi check_prefix_var init_database init_wine_ver @@ -2072,8 +2179,8 @@ wh_info () { install [скрипт] запустить скрипт установки программы install [скрипт] --clear-pfx не использовать готовый префикс для установки ПО - install-dxvk [версия|none] установить/удалить DXVK в выбранный префикс - install-vkd3d [версия|none] установить/удалить VKD3D в выбранный префикс + install-dxvk [версия|none|list] установить, удалить или показать версии DXVK + install-vkd3d [версия|none|list] установить, удалить или показать версии VKD3D installed список установленных программ run [программа] запуск программы (отладка) From 13efa924d8304000f1727bfe8a820e98bc843fc3 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Tue, 9 Sep 2025 13:31:39 +0600 Subject: [PATCH 10/11] added auto-completion for installing dxvk/vkd3d --- auto_completion/bash_completion/winehelper | 2 +- auto_completion/zsh_completion/_winehelper | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/auto_completion/bash_completion/winehelper b/auto_completion/bash_completion/winehelper index 83c8109..2934046 100644 --- a/auto_completion/bash_completion/winehelper +++ b/auto_completion/bash_completion/winehelper @@ -4,7 +4,7 @@ _winehelper_completions() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="--help --version --debug install installed -r -i remove-all --clear-pfx killall remove-prefix backup-prefix restore-prefix create-prefix --changelog changelog" + opts="--help --version --debug install installed install-dxvk install-vkd3d -r -i remove-all --clear-pfx killall remove-prefix backup-prefix restore-prefix create-prefix --changelog changelog" wine_cmd="winecfg winereg winefile wineconsole winetricks desktop regedit explorer cmd run" case "${prev}" in diff --git a/auto_completion/zsh_completion/_winehelper b/auto_completion/zsh_completion/_winehelper index fc12c01..ac9aded 100644 --- a/auto_completion/zsh_completion/_winehelper +++ b/auto_completion/zsh_completion/_winehelper @@ -8,6 +8,8 @@ _winehelper() { '--version[Показать информацию о пакете и его версии]' '--debug[Режим отладки]' 'install[Запустить скрипт установки программы]' + 'install-dxvk[Установить/удалить DXVK]' + 'install-vkd3d[Установить/удалить VKD3D]' 'installed[Список установленных программ]' '-r[Запуск программы (отладка)]' '-i[Запустить скрипт установки программы]' @@ -61,6 +63,12 @@ _winehelper() { restore-prefix) _files ;; + install-dxvk) + _get_component_versions 'install-dxvk' + ;; + install-vkd3d) + _get_component_versions 'install-vkd3d' + ;; *) _values 'winehelper options' "${opts[@]}" "${wine_cmd[@]}" ;; @@ -69,6 +77,16 @@ _winehelper() { esac } +_get_component_versions () { + local component_command=$1 + local -a versions + + versions=( ${(f)"$(winehelper "${component_command}" list 2>/dev/null | grep ' - ' | sed 's/ - //')" } ) + versions+=(none list) + + _values 'versions' "${versions[@]}" +} + _get_prefixes () { prefixes=( ${(f)"$(ls -1 ~/.local/share/winehelper/prefixes 2>/dev/null)"} ) From bf3a30487eb43a7366fa3d0e28d0bb44c695f78b Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Tue, 9 Sep 2025 15:19:55 +0600 Subject: [PATCH 11/11] fixed paths for determining the installation of system dependencies --- winehelper_gui.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index a5639e9..d577564 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -42,10 +42,14 @@ class DependencyManager: def _get_dependencies_path(self): """Определяет и возвращает путь к скрипту dependencies.sh.""" - winehelper_script_path = os.environ.get("RUN_SCRIPT") - if winehelper_script_path and os.path.exists(winehelper_script_path): - return os.path.join(os.path.dirname(winehelper_script_path), 'dependencies.sh') - return None + if Var.DATA_PATH: + base_path = Var.DATA_PATH + elif Var.RUN_SCRIPT and os.path.exists(Var.RUN_SCRIPT): + base_path = os.path.dirname(Var.RUN_SCRIPT) + else: + return None + + return os.path.join(base_path, 'dependencies.sh') def _calculate_file_hash(self): """Вычисляет хэш SHA256 файла зависимостей."""