added control buttons for dxvk/vkd3d

This commit is contained in:
Sergey Palcheh
2025-09-08 21:17:45 +06:00
parent a57df9a259
commit 1e19fa3c56
2 changed files with 331 additions and 19 deletions

View File

@@ -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 "$@" ;;

View File

@@ -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'<p style="line-height: 1.3; font-size: 9pt;">'
html_content += f"<b>Имя:</b> {html.escape(prefix_name)}<br>"
@@ -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):
"""
Открывает диалог для создания ярлыка для приложения внутри выбранного префикса.