added a button to create a program shortcut

the function of extracting an icon from an exe file is combined with the creation of a desktop file
added the function of automatic prefix detection if it is not specified
This commit is contained in:
Sergey Palcheh
2025-09-02 16:23:39 +06:00
parent 92a682ba8a
commit ab0e23952e
2 changed files with 254 additions and 79 deletions

View File

@@ -12,7 +12,7 @@ import hashlib
from functools import partial
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QPushButton, QLabel, QTabWidget, QTabBar,
QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox,
QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser)
QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog)
from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QDesktopServices
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
@@ -965,7 +965,8 @@ class ScriptParser:
if part == 'create_desktop':
if len(parts) > i + 3:
icon_name = parts[i + 3]
if icon_name:
# Игнорируем служебные слова, которые не являются иконками
if icon_name and icon_name.lower() not in ('auto', 'nocopy'):
icon_names.append(icon_name)
except (ValueError, IndexError):
continue
@@ -1975,7 +1976,7 @@ class WineHelperGUI(QMainWindow):
install_path_layout = QHBoxLayout()
self.prefix_install_path_edit = QLineEdit()
self.prefix_install_path_edit.setPlaceholderText("Путь к .exe или .msi файлу...")
self.prefix_install_path_edit.setPlaceholderText("Укажите путь к установочному файлу .exe или .msi...")
install_path_layout.addWidget(self.prefix_install_path_edit)
self.prefix_browse_button = QPushButton("Обзор...")
@@ -1983,10 +1984,21 @@ class WineHelperGUI(QMainWindow):
install_path_layout.addWidget(self.prefix_browse_button)
install_layout.addLayout(install_path_layout)
# Layout для кнопок установки и создания ярлыка
action_buttons_layout = QHBoxLayout()
self.prefix_install_button = QPushButton("Установить приложение в префикс")
self.prefix_install_button.setEnabled(False)
self.prefix_install_button.clicked.connect(self.run_prefix_installer)
install_layout.addWidget(self.prefix_install_button)
action_buttons_layout.addWidget(self.prefix_install_button)
self.create_launcher_button = QPushButton("Создать ярлык для приложения в префиксе")
self.create_launcher_button.setToolTip(
"Создает ярлык в меню и на вкладке 'Установленные' для .exe файла внутри префикса.")
self.create_launcher_button.clicked.connect(self.create_launcher_for_prefix)
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, 4, 0, 1, 3)
@@ -2001,6 +2013,19 @@ 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]
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):
@@ -2075,31 +2100,59 @@ class WineHelperGUI(QMainWindow):
msg_box.setIcon(QMessageBox.Question)
msg_box.setWindowTitle('Подтверждение удаления')
msg_box.setText(f'Вы уверены, что хотите удалить префикс "{prefix_name}"?\n\n'
'Это действие необратимо и удалит все данные внутри префикса.')
'Это действие необратимо и удалит все данные внутри префикса, а также все связанные с ним ярлыки.')
yes_button = msg_box.addButton("Да", QMessageBox.YesRole)
yes_button = msg_box.addButton("Да, удалить", QMessageBox.YesRole)
no_button = msg_box.addButton("Нет", QMessageBox.NoRole)
msg_box.setDefaultButton(no_button)
msg_box.exec_()
# Если пользователь нажал не "Да", выходим
if msg_box.clickedButton() != yes_button:
return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
# Используем модальный диалог для отображения процесса удаления
self.command_dialog = QDialog(self)
self.command_dialog.setWindowTitle(f"Удаление префикса: {prefix_name}")
self.command_dialog.setMinimumSize(750, 400)
self.command_dialog.setModal(True)
self.command_dialog.setWindowFlags(self.command_dialog.windowFlags() & ~Qt.WindowCloseButtonHint)
try:
if os.path.isdir(prefix_path):
shutil.rmtree(prefix_path)
if prefix_name in self.created_prefixes_info:
del self.created_prefixes_info[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)
QMessageBox.information(self, "Успех", f"Префикс '{prefix_name}' был успешно удален.")
except Exception as e:
QMessageBox.critical(self, "Ошибка удаления", f"Не удалось удалить префикс '{prefix_name}':\n{e}")
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_prefix_deletion_finished(prefix_name, exit_code, exit_status)
)
args = ["remove-prefix", prefix_name, "--force"]
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_prefix_deletion_finished(self, prefix_name, exit_code, exit_status):
"""Обрабатывает завершение процесса удаления префикса."""
self._handle_command_finished(exit_code, exit_status)
if exit_code == 0:
# Успешное удаление, обновляем GUI
self._remove_prefix_from_gui_state(prefix_name)
self.update_installed_apps()
QMessageBox.information(self, "Успех", f"Префикс '{prefix_name}' и все связанные с ним данные были успешно удалены.")
else:
QMessageBox.critical(self, "Ошибка удаления", f"Не удалось удалить префикс '{prefix_name}'.\nПодробности смотрите в логе.")
def on_prefix_name_edited(self, text):
"""Сбрасывает состояние управления префиксом, когда пользователь вводит новое имя."""
@@ -2116,6 +2169,9 @@ class WineHelperGUI(QMainWindow):
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):
@@ -2197,6 +2253,79 @@ class WineHelperGUI(QMainWindow):
self.command_process.start(wine_executable, args)
self.command_dialog.exec_()
def create_launcher_for_prefix(self):
"""
Открывает диалог для создания ярлыка для приложения внутри выбранного префикса.
"""
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)
drive_c_path = os.path.join(prefix_path, "drive_c")
if not os.path.isdir(drive_c_path):
QMessageBox.critical(self, "Ошибка", f"Диск C: для префикса '{prefix_name}' не найден.")
return
# 1. Открываем диалог выбора файла для .exe
exe_path, _ = QFileDialog.getOpenFileName(
self,
"Выберите исполняемый файл (.exe) для создания ярлыка",
drive_c_path,
"Исполняемые файлы (*.exe)"
)
if not exe_path:
return # Пользователь отменил
# 2. Запрашиваем имя для ярлыка
app_name, ok = QInputDialog.getText(
self,
"Имя ярлыка",
"Введите имя для нового ярлыка:",
QLineEdit.Normal,
os.path.splitext(os.path.basename(exe_path))[0] # Предлагаем имя из .exe
)
if not ok or not app_name.strip():
return # Пользователь отменил или ввел пустое имя
# 3. Вызываем winehelper.sh create-desktop
self.command_dialog = QDialog(self)
self.command_dialog.setWindowTitle(f"Создание ярлыка для: {app_name}")
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(self._handle_launcher_creation_finished)
env = QProcessEnvironment.systemEnvironment()
env.insert("WINEPREFIX", prefix_path)
self.command_process.setProcessEnvironment(env)
args = ["desktop", app_name, exe_path, "auto"]
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_help_tab(self):
"""Создает вкладку 'Справка' с подвкладками"""
help_tab = QWidget()
@@ -2503,6 +2632,7 @@ class WineHelperGUI(QMainWindow):
"""Обрабатывает завершение установки в префикс."""
if exit_code == 0:
self.command_log_output.append("\n=== Установка успешно завершена ===")
self.create_launcher_button.setEnabled(True) # Активируем кнопку создания ярлыка
else:
self.command_log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===")
@@ -2962,6 +3092,9 @@ class WineHelperGUI(QMainWindow):
except Exception as e:
raise RuntimeError(f"Ошибка удаления префикса: {str(e)}")
# Обновляем состояние на вкладке "Создать префикс"
self._remove_prefix_from_gui_state(prefix_name)
# 3. Удаляем ВСЕ найденные .desktop файлы, связанные с этим префиксом
removed_files = []
for file_path in all_desktop_files:
@@ -3514,6 +3647,18 @@ class WineHelperGUI(QMainWindow):
self.command_process = None
self.command_close_button.setEnabled(True)
def _handle_launcher_creation_finished(self, exit_code, exit_status):
"""Обрабатывает завершение создания ярлыка."""
self._handle_command_finished(exit_code, exit_status)
if exit_code == 0:
QMessageBox.information(self, "Успех", "Ярлык успешно создан.")
self.update_installed_apps()
# Переключаемся на вкладку "Установленные"
for i in range(self.tab_bar.count()):
if self.tab_bar.tabText(i) == "Установленные":
self.tab_bar.setCurrentIndex(i)
break
def _handle_restore_finished(self, exit_code, exit_status):
"""Обрабатывает завершение для диалога команды восстановления."""
if exit_code == 0: