added a file association button

This commit is contained in:
Sergey Palcheh
2025-09-25 13:29:56 +06:00
parent 9f994a8cc3
commit 97996fb67b
2 changed files with 177 additions and 4 deletions

View File

@@ -1179,6 +1179,16 @@ init_wineprefix () {
# добавление ассоциаций файлов для запуска нативного приложения из wine # добавление ассоциаций файлов для запуска нативного приложения из wine
# пример переменной: WH_XDG_OPEN="txt doc pdf" # пример переменной: WH_XDG_OPEN="txt doc pdf"
check_variables WH_XDG_OPEN "0" check_variables WH_XDG_OPEN "0"
# Сохраняем старые ассоциации, чтобы потом удалить те, что больше не нужны
local old_xdg_open
if [[ -f "$WINEPREFIX/last.conf" ]]; then
old_xdg_open=$(grep "WH_XDG_OPEN=" "$WINEPREFIX/last.conf" | sed -e 's/.*WH_XDG_OPEN="//' -e 's/"$//')
fi
# Если переменная WH_XDG_OPEN была установлена извне (например, из GUI),
# то мы должны принудительно установить ее значение в "0", если она пуста,
# чтобы корректно удалить старые ассоциации.
[[ -z "$WH_XDG_OPEN" ]] && WH_XDG_OPEN="0"
local WRAPPER="${WH_TMP_DIR}/wh-xdg-open.sh" local WRAPPER="${WH_TMP_DIR}/wh-xdg-open.sh"
local XDG_OPEN_REG="Software\Classes\xdg-open\shell\open\command" local XDG_OPEN_REG="Software\Classes\xdg-open\shell\open\command"
if [[ $WH_XDG_OPEN != "0" ]] ; then if [[ $WH_XDG_OPEN != "0" ]] ; then
@@ -1201,13 +1211,25 @@ init_wineprefix () {
# добавляем новую команду xdg-open в реестр # добавляем новую команду xdg-open в реестр
get_and_set_reg_file --add "$XDG_OPEN_REG" '@=' 'REG_SZ' "$WRAPPER %1" "system" get_and_set_reg_file --add "$XDG_OPEN_REG" '@=' 'REG_SZ' "$WRAPPER %1" "system"
# Удаляем старые ассоциации, которых нет в новом списке
if [[ -n "$old_xdg_open" ]]; then
for old_ext in $old_xdg_open; do
if ! echo " $WH_XDG_OPEN " | grep -q " $old_ext "; then
get_and_set_reg_file --delete "Software\Classes\.$old_ext" '@='
fi
done
fi
# добавляем ассоциации файлов для запуска с помощью xdg-open # добавляем ассоциации файлов для запуска с помощью xdg-open
for ext in $WH_XDG_OPEN ; do for ext in $WH_XDG_OPEN ; do
get_and_set_reg_file --add "Software\Classes\.$ext" '@=' 'REG_SZ' "xdg-open" "system" get_and_set_reg_file --add "Software\Classes\.$ext" '@=' 'REG_SZ' "xdg-open" "system"
done done
print_info "Используются ассоциации с нативными приложениями для файлов: \"$WH_XDG_OPEN\"" print_info "Используются ассоциации с нативными приложениями для файлов: \"$WH_XDG_OPEN\""
else else
# удаление команды xdg-open из реестра # удаление всех ассоциаций
for old_ext in $old_xdg_open; do
get_and_set_reg_file --delete "Software\Classes\.$old_ext" '@='
done
get_and_set_reg_file --delete "$XDG_OPEN_REG" '@=' get_and_set_reg_file --delete "$XDG_OPEN_REG" '@='
# удаяем скрипт-обёртку # удаяем скрипт-обёртку
try_remove_file "$WRAPPER" try_remove_file "$WRAPPER"
@@ -1348,6 +1370,13 @@ init_database () {
. "$WHDB_FILE" . "$WHDB_FILE"
elif check_prefix_var && [[ -f "$WINEPREFIX/last.conf" ]] ; then elif check_prefix_var && [[ -f "$WINEPREFIX/last.conf" ]] ; then
print_info "Найдены настройки из предыдущего использования префикса: $WINEPREFIX" print_info "Найдены настройки из предыдущего использования префикса: $WINEPREFIX"
# Сохраняем значение WH_XDG_OPEN, если оно было установлено извне (например, из GUI).
# Это предотвращает его перезапись старым значением из last.conf.
# Используем `declare -p` для надежного определения, была ли переменная установлена,
# даже если она пустая (что означает "удалить все ассоциации").
if declare -p WH_XDG_OPEN &>/dev/null; then
wh_xdg_open_from_env="$WH_XDG_OPEN"
fi
cat "$WINEPREFIX/last.conf" cat "$WINEPREFIX/last.conf"
. "$WINEPREFIX/last.conf" . "$WINEPREFIX/last.conf"
else else
@@ -1356,10 +1385,19 @@ init_database () {
} }
prepair_wine () { prepair_wine () {
# Объявляем переменную здесь, чтобы она была доступна после вызова init_database
local wh_xdg_open_from_env
if [[ -n "$INSTALL_SCRIPT_NAME" ]] if [[ -n "$INSTALL_SCRIPT_NAME" ]]
then print_info "Используются настройки из скрипта установки: $INSTALL_SCRIPT_NAME" then print_info "Используются настройки из скрипта установки: $INSTALL_SCRIPT_NAME"
else init_database else init_database
fi fi
# Восстанавливаем значение WH_XDG_OPEN, если оно было установлено извне.
# Проверяем, была ли переменная сохранена, а не ее значение.
# Это позволяет корректно передать пустую строку.
if declare -p wh_xdg_open_from_env &>/dev/null; then
export WH_XDG_OPEN="$wh_xdg_open_from_env"
fi
init_wine_ver init_wine_ver
init_wineprefix init_wineprefix
use_winetricks use_winetricks

View File

@@ -12,7 +12,7 @@ import hashlib
from functools import partial from functools import partial
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QPushButton, QLabel, QTabWidget, QTabBar, from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QPushButton, QLabel, QTabWidget, QTabBar,
QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox, QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox,
QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog) QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog, QDialogButtonBox)
from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QDesktopServices from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QDesktopServices
from PyQt5.QtNetwork import QLocalServer, QLocalSocket from PyQt5.QtNetwork import QLocalServer, QLocalSocket
@@ -1332,6 +1332,75 @@ class CreatePrefixDialog(QDialog):
self.accept() self.accept()
class FileAssociationsDialog(QDialog):
"""Диалог для управления ассоциациями файлов (WH_XDG_OPEN)."""
def __init__(self, current_associations, parent=None):
super().__init__(parent)
self.setWindowTitle("Настройка ассоциаций файлов")
self.setMinimumWidth(450)
self.setModal(True)
self.new_associations = current_associations
layout = QVBoxLayout(self)
layout.setSpacing(10) # Добавляем вертикальный отступ между виджетами
info_label = QLabel(
"Укажите расширения файлов, которые должны открываться нативными<br>"
"приложениями Linux. Чтобы удалить все ассоциации, очистите поле.<br><br>"
"<b>Пример:</b> <code>pdf docx txt</code>"
)
info_label.setWordWrap(True)
info_label.setTextFormat(Qt.RichText)
layout.addWidget(info_label)
self.associations_edit = QLineEdit()
# Если ассоциации не заданы (значение "0"), поле будет пустым, чтобы показать подсказку
if current_associations != "0":
self.associations_edit.setText(current_associations)
self.associations_edit.setPlaceholderText("Введите расширения через пробел...")
layout.addWidget(self.associations_edit)
# Запрещенные расширения
forbidden_label = QLabel(
"<small><b>Запрещено использовать:</b> cpl, dll, exe, lnk, msi</small>"
)
forbidden_label.setTextFormat(Qt.RichText) # Включаем обработку HTML
layout.addWidget(forbidden_label)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.validate_and_accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
def validate_and_accept(self):
"""Проверяет введенные данные перед закрытием."""
forbidden_extensions = {"cpl", "dll", "exe", "lnk", "msi"}
# Получаем введенные расширения, очищаем от лишних пробелов
input_text = self.associations_edit.text().lower().strip()
entered_extensions = {ext.strip() for ext in input_text.split() if ext.strip()}
found_forbidden = entered_extensions.intersection(forbidden_extensions)
if found_forbidden:
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Warning)
msg_box.setWindowTitle("Недопустимые расширения")
msg_box.setTextFormat(Qt.RichText)
msg_box.setText(
"Следующие расширения запрещены и не могут быть использованы:<br><br>"
f"<b>{', '.join(sorted(list(found_forbidden)))}</b>"
)
msg_box.exec_()
return
# Сохраняем результат в виде отсортированной строки
self.new_associations = " ".join(sorted(list(entered_extensions)))
self.accept()
class ComponentVersionSelectionDialog(QDialog): class ComponentVersionSelectionDialog(QDialog):
"""Диалог для выбора версии компонента (DXVK, VKD3D).""" """Диалог для выбора версии компонента (DXVK, VKD3D)."""
@@ -2119,6 +2188,13 @@ class WineHelperGUI(QMainWindow):
self.vkd3d_manage_button.setToolTip("Установка или удаление определенной версии vkd3d-proton в префиксе.") self.vkd3d_manage_button.setToolTip("Установка или удаление определенной версии vkd3d-proton в префиксе.")
management_layout.addWidget(self.vkd3d_manage_button, 5, 1) management_layout.addWidget(self.vkd3d_manage_button, 5, 1)
self.file_associations_button = QPushButton("Ассоциации файлов")
self.file_associations_button.setMinimumHeight(32)
self.file_associations_button.clicked.connect(self.open_file_associations_manager)
self.file_associations_button.setToolTip(
"Настройка открытия определенных типов файлов с помощью нативных приложений Linux.")
management_layout.addWidget(self.file_associations_button, 6, 0, 1, 2)
# --- Правая сторона: Информационный блок и кнопки установки --- # --- Правая сторона: Информационный блок и кнопки установки ---
right_column_widget = QWidget() right_column_widget = QWidget()
right_column_layout = QVBoxLayout(right_column_widget) right_column_layout = QVBoxLayout(right_column_widget)
@@ -2151,7 +2227,7 @@ class WineHelperGUI(QMainWindow):
right_column_layout.setStretch(0, 1) # Информационное окно растягивается right_column_layout.setStretch(0, 1) # Информационное окно растягивается
right_column_layout.setStretch(1, 0) # Группа кнопок не растягивается right_column_layout.setStretch(1, 0) # Группа кнопок не растягивается
management_layout.addWidget(right_column_widget, 0, 2, 6, 1) management_layout.addWidget(right_column_widget, 0, 2, 7, 1)
management_layout.setColumnStretch(0, 1) management_layout.setColumnStretch(0, 1)
management_layout.setColumnStretch(1, 1) management_layout.setColumnStretch(1, 1)
@@ -2358,8 +2434,9 @@ class WineHelperGUI(QMainWindow):
"VKD3D_VER": ("Версия VKD3D", lambda v: v if v else "Не установлено"), "VKD3D_VER": ("Версия VKD3D", lambda v: v if v else "Не установлено"),
"WINEESYNC": ("ESync", lambda v: "Включен" if v == "1" else "Выключен"), "WINEESYNC": ("ESync", lambda v: "Включен" if v == "1" else "Выключен"),
"WINEFSYNC": ("FSync", lambda v: "Включен" if v == "1" else "Выключен"), "WINEFSYNC": ("FSync", lambda v: "Включен" if v == "1" else "Выключен"),
"WH_XDG_OPEN": ("Ассоциации файлов", lambda v: v if v and v != "0" else "Не заданы"),
} }
display_order = ["WINEPREFIX", "WINEARCH", "WH_WINE_USE", "BASE_PFX", "DXVK_VER", "VKD3D_VER", "WINEESYNC", "WINEFSYNC"] display_order = ["WINEPREFIX", "WINEARCH", "WH_WINE_USE", "BASE_PFX", "DXVK_VER", "VKD3D_VER", "WINEESYNC", "WINEFSYNC", "WH_XDG_OPEN"]
html_content = f'<p style="line-height: 1.3; font-size: 9pt;">' html_content = f'<p style="line-height: 1.3; font-size: 9pt;">'
html_content += f"<b>Имя:</b> {html.escape(prefix_name)}<br>" html_content += f"<b>Имя:</b> {html.escape(prefix_name)}<br>"
@@ -2659,6 +2736,64 @@ class WineHelperGUI(QMainWindow):
if exit_code == 0: if exit_code == 0:
self.update_prefix_info_display(prefix_name) self.update_prefix_info_display(prefix_name)
def open_file_associations_manager(self):
"""Открывает диалог для управления ассоциациями файлов."""
prefix_name = self.current_managed_prefix_name
if not prefix_name:
QMessageBox.warning(self, "Ошибка", "Сначала выберите префикс.")
return
current_associations = self._get_prefix_component_version(prefix_name, "WH_XDG_OPEN") or ""
dialog = FileAssociationsDialog(current_associations, self)
if dialog.exec_() == QDialog.Accepted:
new_associations = dialog.new_associations
# Запускаем обновление, только если значение изменилось
if new_associations != current_associations:
self.run_update_associations_command(prefix_name, new_associations)
def run_update_associations_command(self, prefix_name, new_associations):
"""Выполняет команду обновления ассоциаций файлов."""
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
self.command_dialog = QDialog(self)
self.command_dialog.setWindowTitle("Обновление ассоциаций файлов")
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)
# Устанавливаем новую переменную окружения для скрипта
env.insert("WH_XDG_OPEN", new_associations)
self.command_process.setProcessEnvironment(env)
args = ["init-prefix"]
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 create_launcher_for_prefix(self): def create_launcher_for_prefix(self):
""" """
Открывает диалог для создания ярлыка для приложения внутри выбранного префикса. Открывает диалог для создания ярлыка для приложения внутри выбранного префикса.