From 08090bbb6b180061900c151c58a82c0b7ac77ed7 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 11 Sep 2025 10:45:30 +0600 Subject: [PATCH 1/5] fixed the winetricks detection path --- winehelper_gui.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index d577564..324e4cb 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -3196,20 +3196,20 @@ class WineHelperGUI(QMainWindow): QMessageBox.critical(self, "Ошибка", f"Каталог префикса не найден:\n{prefix_path}") return - winehelper_dir = os.path.dirname(self.winehelper_path) + winetricks_search_dir = Var.DATA_PATH winetricks_path = None try: # Ищем файл, который начинается с 'winetricks_' - for filename in os.listdir(winehelper_dir): + for filename in os.listdir(winetricks_search_dir): if filename.startswith("winetricks_"): - winetricks_path = os.path.join(winehelper_dir, filename) + winetricks_path = os.path.join(winetricks_search_dir, filename) break # Нашли, выходим из цикла except OSError as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось прочитать директорию {winehelper_dir}: {e}") + QMessageBox.critical(self, "Ошибка", f"Не удалось прочитать директорию {winetricks_search_dir}: {e}") return if not winetricks_path: - QMessageBox.critical(self, "Ошибка", f"Скрипт winetricks не найден в директории:\n{winehelper_dir}") + QMessageBox.critical(self, "Ошибка", f"Скрипт winetricks не найден в директории:\n{winetricks_search_dir}") return wine_executable = self._get_wine_executable_for_prefix(prefix_name) From aa591112ff6ff6b3f4ed8c17247df814bcc7c981 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 11 Sep 2025 11:12:17 +0600 Subject: [PATCH 2/5] simplifying the definition of the path to dependencies.sh --- winehelper_gui.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 324e4cb..a3ea958 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -42,14 +42,10 @@ class DependencyManager: def _get_dependencies_path(self): """Определяет и возвращает путь к скрипту dependencies.sh.""" - 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: + if not Var.DATA_PATH: return None - return os.path.join(base_path, 'dependencies.sh') + return os.path.join(Var.DATA_PATH, 'dependencies.sh') def _calculate_file_hash(self): """Вычисляет хэш SHA256 файла зависимостей.""" From bab49377a3a40cceeebacb3a8c949d8c47760355 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 11 Sep 2025 12:52:04 +0600 Subject: [PATCH 3/5] added the wine/proton control button to the created prefix --- winehelper | 30 ++++++++++-- winehelper_gui.py | 116 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 135 insertions(+), 11 deletions(-) diff --git a/winehelper b/winehelper index d4fdee4..a2a7d44 100755 --- a/winehelper +++ b/winehelper @@ -1652,8 +1652,8 @@ select_wine_version() { read -p "Введите номер для выбора wine/proton (0-$max_choice): " user_choice if [[ "$user_choice" =~ ^[0-9]+$ ]] && (( user_choice >= 0 && user_choice <= max_choice )); then if [[ "$user_choice" == "0" ]]; then - print_info "Создание префикса отменено." - exit 0 + print_info "Операция отменена." + return 1 fi local selected_opt selected_opt="${selectable_options[$user_choice]}" @@ -1667,6 +1667,7 @@ select_wine_version() { print_error "Неверный выбор. Введите число от 0 до $max_choice." fi done + return 0 } create_prefix() { @@ -1716,7 +1717,7 @@ create_prefix() { *) fatal "Неверный выбор. Операция отменена." ;; esac - select_wine_version + select_wine_version || exit 0 print_info "Выберите тип создаваемого префикса:" echo " 0) Отмена создания префикса" @@ -2171,6 +2172,27 @@ run_install_vkd3d() { wait_wineserver } +run_change_wine_version() { + local new_version="$1" + + check_prefix_var + init_database + + if [[ -z "$new_version" ]]; then + select_wine_version || exit 0 + new_version="$WH_WINE_USE" + else + export WH_WINE_USE="$new_version" + fi + + init_wine_ver + + init_wineprefix + + wait_wineserver + print_ok "Версия Wine для префикса $PREFIX_NAME успешно изменена на $WH_WINE_USE." +} + wh_info () { echo "Использование: $SCRIPT_NAME [команда] @@ -2181,6 +2203,7 @@ wh_info () { install-dxvk [версия|none|list] установить, удалить или показать версии DXVK install-vkd3d [версия|none|list] установить, удалить или показать версии VKD3D + change-wine [версия] изменить версию Wine/Proton для текущего префикса installed список установленных программ run [программа] запуск программы (отладка) @@ -2231,6 +2254,7 @@ case "$arg1" in install|-i) run_autoinstall "$@" ;; install-dxvk) run_install_dxvk "$@" ;; install-vkd3d) run_install_vkd3d "$@" ;; + change-wine) run_change_wine_version "$@" ;; 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 a3ea958..3817227 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -2105,29 +2105,35 @@ class WineHelperGUI(QMainWindow): self.prefix_winefile_button.setToolTip("Запуск файлового менеджера Wine (winefile) для просмотра файлов внутри префикса.") management_layout.addWidget(self.prefix_winefile_button, 2, 1) + self.change_wine_version_button = QPushButton("Управление Wine/Proton") + self.change_wine_version_button.setMinimumHeight(32) + self.change_wine_version_button.clicked.connect(self.open_wine_version_manager) + self.change_wine_version_button.setToolTip("Изменение версии Wine или Proton для выбранного префикса.") + management_layout.addWidget(self.change_wine_version_button, 3, 0, 1, 2) + # Добавляем небольшой отступ spacer_widget = QWidget() spacer_widget.setFixedHeight(5) - management_layout.addWidget(spacer_widget, 3, 0, 1, 2) + management_layout.addWidget(spacer_widget, 4, 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) + management_layout.addWidget(self.dxvk_manage_button, 5, 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) + management_layout.addWidget(self.vkd3d_manage_button, 5, 1) # --- Правая сторона: Информационный блок --- self.prefix_info_display = QTextBrowser() self.prefix_info_display.setReadOnly(True) self.prefix_info_display.setFrameStyle(QFrame.StyledPanel) # Увеличиваем rowspan, чтобы учесть добавленный отступ - management_layout.addWidget(self.prefix_info_display, 0, 2, 5, 1) + management_layout.addWidget(self.prefix_info_display, 0, 2, 6, 1) management_layout.setColumnStretch(0, 1) management_layout.setColumnStretch(1, 1) @@ -2137,7 +2143,7 @@ class WineHelperGUI(QMainWindow): separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setFrameShadow(QFrame.Sunken) - management_layout.addWidget(separator, 5, 0, 1, 3) + management_layout.addWidget(separator, 6, 0, 1, 3) install_group = QWidget() install_layout = QVBoxLayout(install_group) @@ -2169,8 +2175,7 @@ class WineHelperGUI(QMainWindow): self.create_launcher_button.setEnabled(False) # Изначально неактивна action_buttons_layout.addWidget(self.create_launcher_button) install_layout.addLayout(action_buttons_layout) - - management_layout.addWidget(install_group, 6, 0, 1, 3) + management_layout.addWidget(install_group, 7, 0, 1, 3) container_layout.addWidget(self.prefix_management_groupbox) layout.addWidget(self.management_container_groupbox) @@ -2528,6 +2533,81 @@ class WineHelperGUI(QMainWindow): # Если лицензия принята, запускаем установку. self.run_component_install_command(prefix_name, command, version) + def open_wine_version_manager(self): + """Открывает диалог для смены версии Wine/Proton для префикса.""" + prefix_name = self.current_managed_prefix_name + if not prefix_name: + QMessageBox.warning(self, "Ошибка", "Сначала выберите префикс.") + return + + # Определяем архитектуру префикса + prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + last_conf_path = os.path.join(prefix_path, "last.conf") + architecture = "win64" # По умолчанию + if os.path.exists(last_conf_path): + try: + with open(last_conf_path, 'r', encoding='utf-8') as f: + for line in f: + if 'WINEARCH=' in line: + arch_val = line.split('=', 1)[1].strip().strip('"\'') + if arch_val: + architecture = arch_val + break + except Exception as e: + print(f"Предупреждение: не удалось прочитать архитектуру из {last_conf_path}: {e}") + + dialog = WineVersionSelectionDialog(architecture, self) + if dialog.exec_() == QDialog.Accepted and dialog.selected_version: + new_version = dialog.selected_version + new_version_display = dialog.selected_display_text + + if not self._show_license_agreement_dialog(): + return # Пользователь отклонил лицензию + + self.run_change_wine_version_command(prefix_name, new_version, new_version_display) + + def run_change_wine_version_command(self, prefix_name, new_version, new_version_display): + """Выполняет команду смены версии Wine/Proton через winehelper.""" + self.command_dialog = QDialog(self) + self.command_dialog.setWindowTitle(f"Смена версии Wine на: {new_version_display}") + 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_output_buffer = "" + self.command_last_line_was_progress = False + + self.command_process = QProcess(self.command_dialog) + self.command_process.setProcessChannelMode(QProcess.MergedChannels) + self.command_process.readyReadStandardOutput.connect(self._handle_prefix_creation_output) + self.command_process.finished.connect( + lambda exit_code, exit_status: self._handle_change_wine_version_finished( + prefix_name, exit_code, exit_status + ) + ) + + env = QProcessEnvironment.systemEnvironment() + env.insert("WINEPREFIX", os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)) + self.command_process.setProcessEnvironment(env) + + args = ["change-wine", new_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 run_component_install_command(self, prefix_name, command, version): """Выполняет команду установки компонента (DXVK/VKD3D) через winehelper.""" prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) @@ -2568,11 +2648,31 @@ class WineHelperGUI(QMainWindow): self.command_process.start(self.winehelper_path, args) self.command_dialog.exec_() + def _handle_change_wine_version_finished(self, prefix_name, exit_code, exit_status): + """Обрабатывает завершение смены версии Wine и обновляет информацию о префиксе.""" + # Обрабатываем остаток в буфере, если он есть + if self.command_output_buffer: + self._process_command_log_line(self.command_output_buffer) + self.command_output_buffer = "" + + # Если последней строкой был прогресс, "завершаем" его переносом строки. + if self.command_last_line_was_progress: + cursor = self.command_log_output.textCursor() + cursor.movePosition(QTextCursor.End) + cursor.insertText("\n") + self.command_last_line_was_progress = False + + # Вызываем общий обработчик для обновления лога и кнопки закрытия + self._handle_command_finished(exit_code, exit_status) + + # В случае успеха обновляем панель информации о префиксе + if exit_code == 0: + self.update_prefix_info_display(prefix_name) + 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) From b98c6e5408251b1994ef0b8396506fe86e56d1b9 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 11 Sep 2025 13:28:50 +0600 Subject: [PATCH 4/5] added auto-completion for change-wine --- auto_completion/bash_completion/winehelper | 16 +++++++++++++++- auto_completion/zsh_completion/_winehelper | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/auto_completion/bash_completion/winehelper b/auto_completion/bash_completion/winehelper index 2934046..e27bf5b 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 install-dxvk install-vkd3d -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 change-wine" wine_cmd="winecfg winereg winefile wineconsole winetricks desktop regedit explorer cmd run" case "${prev}" in @@ -34,6 +34,20 @@ _winehelper_completions() { restore-prefix) return 0 ;; + install-dxvk|install-vkd3d) + local versions=$(winehelper "${prev}" list 2>/dev/null | grep ' - ' | sed 's/ - //') + COMPREPLY=( $(compgen -W "${versions} none list" -- "${cur}") ) + return 0 + ;; + change-wine) + local wine_versions=$(awk ' + /^#+\s*(WINE|WINE_LG|PROTON_LG|PROTON_STEAM)\s*#*$/ { in_group=1 } + /^#+/ { if (! ($0 ~ /^#+\s*(WINE|WINE_LG|PROTON_LG|PROTON_STEAM)\s*#*$/)) in_group=0 } + /^[a-f0-9]{64}/ && in_group { sub(/\.tar\.xz$/, "", $2); print $2 } + ' /usr/share/winehelper/sha256sum.list 2>/dev/null) + COMPREPLY=( $(compgen -W "system ${wine_versions}" -- "${cur}") ) + return 0 + ;; *) ;; esac diff --git a/auto_completion/zsh_completion/_winehelper b/auto_completion/zsh_completion/_winehelper index ac9aded..41a49df 100644 --- a/auto_completion/zsh_completion/_winehelper +++ b/auto_completion/zsh_completion/_winehelper @@ -20,6 +20,7 @@ _winehelper() { 'remove-prefix[Удалить префикс и все связанные данные]' 'backup-prefix[Создать резерную копию префикса]' 'restore-prefix[восстановить префикс из резервной копии "путь/до/whpack"]' + 'change-wine[Изменить версию Wine/Proton для префикса]' ) wine_cmd=( @@ -69,6 +70,9 @@ _winehelper() { install-vkd3d) _get_component_versions 'install-vkd3d' ;; + change-wine) + _get_wine_versions + ;; *) _values 'winehelper options' "${opts[@]}" "${wine_cmd[@]}" ;; @@ -87,6 +91,22 @@ _get_component_versions () { _values 'versions' "${versions[@]}" } +_get_wine_versions () { + local -a versions + local sha256_file="/usr/share/winehelper/sha256sum.list" + + if [[ -f "$sha256_file" ]]; then + versions=( ${(f)"$(awk ' + /^#+\s*(WINE|WINE_LG|PROTON_LG|PROTON_STEAM)\s*#*$/ { in_group=1 } + /^#+/ { if (! ($0 ~ /^#+\s*(WINE|WINE_LG|PROTON_LG|PROTON_STEAM)\s*#*$/)) in_group=0 } + /^[a-f0-9]{64}/ && in_group { sub(/\.tar\.xz$/, "", $2); print $2 } + ' "$sha256_file" 2>/dev/null)"} ) + fi + + versions+=(system) + _values 'wine/proton versions' "${versions[@]}" +} + _get_prefixes () { prefixes=( ${(f)"$(ls -1 ~/.local/share/winehelper/prefixes 2>/dev/null)"} ) From 85bd5fdf5d01c9f93ec95c181ed5f1bfab4d0102 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 11 Sep 2025 15:20:09 +0600 Subject: [PATCH 5/5] the path for reading the THIRD-PARTY file has been fixed --- winehelper | 4 +++- winehelper_gui.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/winehelper b/winehelper index a2a7d44..131eb59 100755 --- a/winehelper +++ b/winehelper @@ -7,7 +7,7 @@ if [[ $(id -u) -eq 0 ]] ; then fi ##### DEFAULT PATH ##### -export SCRIPT_NAME USER_WORK_PATH RUN_SCRIPT DATA_PATH CHANGELOG_FILE WH_ICON_PATH LICENSE_FILE AGREEMENT +export SCRIPT_NAME USER_WORK_PATH RUN_SCRIPT DATA_PATH CHANGELOG_FILE WH_ICON_PATH LICENSE_FILE AGREEMENT THIRD_PARTY_FILE SCRIPT_NAME="$(basename "$0")" if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then @@ -19,6 +19,7 @@ if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then WH_ICON_PATH="$DATA_PATH/image/gui/winehelper.svg" LICENSE_FILE="$(realpath "/usr/share/doc/winehelper"-*/LICENSE)" AGREEMENT="$(realpath "/usr/share/doc/winehelper"-*/LICENSE_AGREEMENT)" + THIRD_PARTY_FILE="$(realpath "/usr/share/doc/winehelper"-*/THIRD_PARTY)" else # переменные для тестового запуска WineHelper из репозитория USER_WORK_PATH="$HOME/test-$SCRIPT_NAME" @@ -28,6 +29,7 @@ else WH_ICON_PATH="$DATA_PATH/image/gui/winehelper-devel.svg" LICENSE_FILE="$DATA_PATH/LICENSE" AGREEMENT="$DATA_PATH/LICENSE_AGREEMENT" + THIRD_PARTY_FILE="$DATA_PATH/THIRD-PARTY" # минимальная проверка синтаксиса скриптов for self_check_script in "$RUN_SCRIPT" \ diff --git a/winehelper_gui.py b/winehelper_gui.py index 3817227..ca1fdf3 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -28,6 +28,7 @@ class Var: WH_ICON_PATH = os.environ.get("WH_ICON_PATH") LICENSE_FILE = os.environ.get("LICENSE_FILE") LICENSE_AGREEMENT_FILE = os.environ.get("AGREEMENT") + THIRD_PARTY_FILE = os.environ.get("THIRD_PARTY_FILE") class DependencyManager: """Класс для управления проверкой и установкой системных зависимостей.""" @@ -2819,8 +2820,8 @@ class WineHelperGUI(QMainWindow): # Читаем и парсим файл THIRD-PARTY third_party_html = "" - third_party_file_path = os.path.join(Var.DATA_PATH, "THIRD-PARTY") - if os.path.exists(third_party_file_path): + third_party_file_path = Var.THIRD_PARTY_FILE + if third_party_file_path and os.path.exists(third_party_file_path): with open(third_party_file_path, 'r', encoding='utf-8') as f_tp: third_party_content = f_tp.read()