Compare commits

..

14 Commits

5 changed files with 165 additions and 62 deletions

View File

@ -1,5 +1,9 @@
История изменений:
0.5.3:
* исправлена установка grdcontrol для t-flex-*
* обновлен графический режим Qt5
0.5.2:
* исправлен запуск winehelper.desktop для winehelper-qt

14
LICENSE_AGREEMENT Normal file
View File

@ -0,0 +1,14 @@
Лицензионные соглашения использования сторонних компонентов:
Некоторые компоненты, установленные в префикс и необходимые для запуска приложений,
могут быть защищены авторским правом или лицензионными соглашениями. Вы обязаны
самостоятельно убедиться в законности использования этих компонентов в вашей
юрисдикции.
Мы не несём ответственности за нарушение лицензионных соглашений, связанное с
использованием подготовленного префикса, а так же за программное обеспечение,
поставляемое из сторонних источников.
Подтверждая продолжение установки, вы соглашаетесь, что ознакомились с данным
отказом от ответственности и принимаете все риски, связанные с использованием
программного обеспечения.

View File

@ -28,8 +28,8 @@ prepair_wine
if [[ -d "$WINEPREFIX" ]] \
&& grep -q "t-flex-cad" "$WINEPREFIX/winetricks.log" \
&& systemctl list-units --type service --state running | grep aksusbd \
&& systemctl list-units --type service --state running | grep hasplmd \
&& systemctl list-units --type service --state running | grep -q aksusbd \
&& systemctl list-units --type service --state running | grep -q hasplmd \
&& rpm -q grdcontrol | grep -q "$GRDCONTROL_VER"
then
print_info "Префикс $PREFIX_NAME готов к установке ПО."
@ -50,7 +50,7 @@ else
if rpm -q grdcontrol | grep -q "$GRDCONTROL_VER"
then print_info "grdcontrol-$GRDCONTROL_VER уже установлен в системе."
else su_run rpm -iv "$AUTOINSTALL_DIR_LIN/Guardant_Linux/grdcontrol.x86_64.rpm"
else su_run "rpm -iv \"$AUTOINSTALL_DIR_LIN/Guardant_Linux/grdcontrol.x86_64.rpm\""
fi
if [[ "$BASE_PFX" == "none" ]] ; then

View File

@ -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
export SCRIPT_NAME USER_WORK_PATH RUN_SCRIPT DATA_PATH CHANGELOG_FILE WH_ICON_PATH LICENSE_FILE AGREEMENT
SCRIPT_NAME="$(basename "$0")"
if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then
@ -18,6 +18,7 @@ if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then
CHANGELOG_FILE="$(realpath "/usr/share/doc/winehelper"-*/CHANGELOG)"
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)"
else
# переменные для тестового запуска WineHelper из репозитория
USER_WORK_PATH="$HOME/test-$SCRIPT_NAME"
@ -26,6 +27,7 @@ else
CHANGELOG_FILE="$DATA_PATH/CHANGELOG"
WH_ICON_PATH="$DATA_PATH/image/gui/winehelper-devel.svg"
LICENSE_FILE="$DATA_PATH/LICENSE"
AGREEMENT="$DATA_PATH/LICENSE_AGREEMENT"
# минимальная проверка синтаксиса скриптов
for self_check_script in "$RUN_SCRIPT" \
@ -367,20 +369,14 @@ print_license_agreement () {
then return 0
fi
if [[ -f "$AGREEMENT" ]]; then
echo
print_warning "Лицензионные соглашения использования сторонних компонентов:
Некоторые компоненты, установленные в префикс и необходимые для запуска приложений, могут
быть защищены авторским правом или лицензионными соглашениями. Вы обязаны самостоятельно
убедиться в законности использования этих компонентов в вашей юрисдикции.
Мы не несём ответственности за нарушение лицензионных соглашений, связанное с использованием
подготовленного префикса, а так же за программное обеспечение поставляемого из сторонних источников.
Подтверждая продолжение установки, вы соглашаетесь что ознакомились с данным отказом от
ответственности и принимаете все риски, связанные с использованием программного обеспечения.
"
print_warning "$(cat "$AGREEMENT")"
else
fatal "Файл лицензионного соглашения не найден: $AGREEMENT"
fi
echo
if print_confirmation "Подтвердите продолжение установки" ; then
touch "$license_agreement_file"
chmod 600 "$license_agreement_file"

View File

@ -2,8 +2,10 @@
import os
import subprocess
import sys
import re
import shlex
import shutil
import html
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QPushButton, QLabel, QTabWidget,
QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea,
QGridLayout, QFrame, QDialog, QTextBrowser)
@ -20,6 +22,7 @@ class Var:
CHANGELOG_FILE = os.environ.get("CHANGELOG_FILE")
WH_ICON_PATH = os.environ.get("WH_ICON_PATH")
LICENSE_FILE = os.environ.get("LICENSE_FILE")
LICENSE_AGREEMENT_FILE = os.environ.get("AGREEMENT")
class WineHelperGUI(QMainWindow):
def __init__(self):
@ -64,6 +67,7 @@ class WineHelperGUI(QMainWindow):
self.process = None
self.current_script = None
self.install_process = None
self.current_display_name = None
self.install_dialog = None
self.current_active_button = None
self.installed_buttons = []
@ -78,10 +82,15 @@ class WineHelperGUI(QMainWindow):
# Создаем табы
self.tabs = QTabWidget()
self.main_layout.addWidget(self.tabs, stretch=2)
self.main_layout.addWidget(self.tabs, stretch=1)
# Создаем панель информации о скрипте
self.create_info_panel()
self.main_layout.addWidget(self.info_panel, stretch=1)
# Фиксируем минимальные размеры
self.tabs.setMinimumWidth(520)
self.info_panel.setMinimumWidth(415)
# Вкладки
self.create_auto_install_tab()
@ -89,6 +98,9 @@ class WineHelperGUI(QMainWindow):
self.create_installed_tab()
self.create_help_tab()
# Инициализируем состояние, которое будет использоваться для логов
self._reset_log_state()
# Обновляем список установленных приложений
self.update_installed_apps()
@ -162,7 +174,7 @@ class WineHelperGUI(QMainWindow):
# Заголовок
self.script_title = QLabel("Выберите программу")
self.script_title.setFont(QFont('Arial', 14, QFont.Bold)) # Шрифт и размер шрифта в заголовке инф. панели
self.script_title.setFont(QFont('Arial', 12, QFont.Bold)) # Шрифт и размер шрифта в заголовке инф. панели
self.script_title.setAlignment(Qt.AlignCenter)
self.info_panel_layout.addWidget(self.script_title)
@ -198,7 +210,7 @@ class WineHelperGUI(QMainWindow):
install_action_layout = QVBoxLayout()
install_action_layout.setContentsMargins(0, 0, 0, 0)
self.install_button = QPushButton("Установить")
self.install_button.setFont(QFont('Arial', 13, QFont.Bold))
self.install_button.setFont(QFont('Arial', 12, QFont.Bold)) # Шрифт и размер шрифта в кнопке Установить
self.install_button.setStyleSheet("background-color: #4CAF50; color: white;")
self.install_button.clicked.connect(self.install_current_script)
install_action_layout.addWidget(self.install_button)
@ -249,8 +261,6 @@ class WineHelperGUI(QMainWindow):
self.installed_action_widget.setVisible(False)
self.installed_global_action_widget.setVisible(False)
self.main_layout.addWidget(self.info_panel, stretch=1)
def browse_install_file(self):
"""Открывает диалог выбора файла для ручной установки"""
file_path, _ = QFileDialog.getOpenFileName(
@ -604,7 +614,6 @@ class WineHelperGUI(QMainWindow):
# Подвкладка "Лицензия"
license_tab = QWidget()
license_layout = QVBoxLayout(license_tab)
import html
license_text = QTextBrowser()
license_text.setOpenExternalLinks(True)
@ -685,9 +694,12 @@ class WineHelperGUI(QMainWindow):
if self.current_active_button in self.installed_buttons:
self.current_active_button = None
# Очистить существующие кнопки
for btn in self.installed_buttons:
btn.deleteLater()
# Полностью очищаем layout перед обновлением, удаляя старые виджеты (рамки с кнопками)
while self.installed_scroll_layout.count():
item = self.installed_scroll_layout.takeAt(0)
widget = item.widget()
if widget:
widget.deleteLater()
self.installed_buttons.clear()
if not os.path.exists(Var.USER_WORK_PATH):
@ -833,6 +845,11 @@ class WineHelperGUI(QMainWindow):
print(f"Error getting prefix name from {desktop_file}: {e}")
return None
def _get_current_app_title(self):
"""Возвращает отображаемое имя для текущей выбранной программы."""
# Если display_name не установлено (например, при ошибке), используем имя скрипта
return self.current_display_name or self.current_script
def backup_prefix_for_app(self):
"""Создает резервную копию префикса для выбранного приложения."""
prefix_name = self._get_prefix_name_for_selected_app()
@ -1206,6 +1223,7 @@ class WineHelperGUI(QMainWindow):
prog_name = self.extract_prog_name_from_script(script_path)
prog_url = self.extract_prog_url_from_script(script_path)
display_name = prog_name if prog_name else script_name
self.current_display_name = display_name
if icon_names:
# Для заголовка используем первую иконку из списка
@ -1228,7 +1246,7 @@ class WineHelperGUI(QMainWindow):
self.install_action_widget.setVisible(True)
self.installed_action_widget.setVisible(False)
self.installed_global_action_widget.setVisible(False)
self.install_button.setText(f"Установить {display_name}")
self.install_button.setText(f"Установить «{display_name}»")
def install_current_script(self):
"""Устанавливает текущий выбранный скрипт"""
@ -1242,7 +1260,8 @@ class WineHelperGUI(QMainWindow):
# Создаем диалоговое окно установки
self.install_dialog = QDialog(self)
self.install_dialog.setWindowTitle(f"Установка {self.current_script}")
title_name = self._get_current_app_title()
self.install_dialog.setWindowTitle(f"Установка «{title_name}»")
self.install_dialog.setMinimumSize(750, 400)
self.install_dialog.setWindowModality(Qt.WindowModal)
@ -1255,38 +1274,32 @@ class WineHelperGUI(QMainWindow):
license_page = QWidget()
license_layout = QVBoxLayout(license_page)
license_text = QTextEdit()
license_text.setReadOnly(True)
license_found = False
# Получаем текст лицензии из скрипта winehelper
script_path = os.path.join(Var.DATA_PATH, "winehelper")
license_content = ""
license_text = QTextBrowser()
# Получаем текст лицензионного соглашения из файла
try:
with open(script_path, 'r', encoding='utf-8') as f:
capturing = False
for line in f:
if 'print_warning "Лицензионные соглашения использования сторонних компонентов:' in line:
capturing = True
continue
license_file_path = Var.LICENSE_AGREEMENT_FILE
if not license_file_path or not os.path.exists(license_file_path):
raise FileNotFoundError
if capturing:
if 'Подтверждая продолжение установки' in line:
break
# Очищаем строку от лишних символов
clean_line = line.strip()
clean_line = clean_line.replace('print_warning "', '').replace('\\n', '\n')
clean_line = clean_line.rstrip('"')
license_content += clean_line + '\n'
with open(license_file_path, 'r', encoding='utf-8') as f:
license_content = f.read()
escaped_license_content = html.escape(license_content)
license_text.setHtml(f"""
<h3>Лицензионные соглашения использования сторонних компонентов:</h3>
<p>{license_content}</p>
<pre style="font-family: sans-serif; font-size: 10pt; white-space: pre-wrap; word-wrap: break-word;">{escaped_license_content}</pre>
""")
license_found = True
except (FileNotFoundError, TypeError):
license_text.setHtml(f'<h3>Лицензионные соглашения</h3><p>Не удалось загрузить файл лицензионного соглашения по пути:<br>{Var.LICENSE_AGREEMENT_FILE}</p>')
except Exception as e:
print(f"Ошибка чтения файла для извлечения лицензии: {str(e)}")
license_text.setHtml("""
print(f"Ошибка чтения файла лицензии: {str(e)}")
license_text.setHtml(f"""
<h3>Лицензионные соглашения</h3>
<p>Не удалось загрузить текст лицензионного соглашения.</p>
<p>Произошла ошибка при чтении файла лицензии:<br>{str(e)}</p>
""")
license_layout.addWidget(license_text)
@ -1333,11 +1346,20 @@ class WineHelperGUI(QMainWindow):
lambda state: self.btn_continue.setEnabled(state == Qt.Checked)
)
if not license_found:
self.license_checkbox.setEnabled(False)
self.install_dialog.show()
def _reset_log_state(self):
"""Сбрасывает состояние буфера и флага прогресса для лога установки."""
self.output_buffer = ""
self.last_line_was_progress = False
def _prepare_installation(self):
"""Подготавливает и запускает процесс установки"""
self.stacked_widget.setCurrentIndex(1)
self._reset_log_state() # Сбрасываем состояние для обработки лога
winehelper_path = self.winehelper_path
script_path = os.path.join(Var.DATA_PATH,
@ -1377,7 +1399,8 @@ class WineHelperGUI(QMainWindow):
if install_file:
args.append(install_file)
self.append_log(f"=== Начало установки {self.current_script} ===")
title_name = self._get_current_app_title()
self.append_log(f"=== Начало установки «{title_name}» ===")
self.append_log(f"Исполняемый файл: {winehelper_path}")
self.append_log(f"Аргументы: {' '.join(shlex.quote(a) for a in args)}")
@ -1385,40 +1408,106 @@ class WineHelperGUI(QMainWindow):
self.install_process.start(winehelper_path, args)
if not self.install_process.waitForStarted(3000):
raise RuntimeError("Не удалось запустить процесс установки")
self.append_log("Процесс установки успешно запущен...")
self.append_log("Процесс установки запущен...")
except Exception as e:
self.append_log(f"\n=== ОШИБКА: {str(e)} ===", is_error=True)
QMessageBox.critical(self.install_dialog, "Ошибка", f"Не удалось запустить установку:\n{str(e)}")
self.cleanup_process()
def append_log(self, text, is_error=False):
def append_log(self, text, is_error=False, add_newline=True):
"""Добавляет сообщение в лог"""
if not hasattr(self, 'log_output'): return
cursor = self.log_output.textCursor()
cursor.movePosition(QTextCursor.End)
if is_error:
# Для ошибок всегда добавляем перенос строки для лучшей читаемости
cursor.insertHtml(f'<font color="red">{text}</font><br>')
else:
cursor.insertText(f"{text}\n")
# Вставляем текст. Добавляем перенос строки, если нужно.
formatted_text = f"{text}\n" if add_newline else text
cursor.insertText(formatted_text)
self.log_output.ensureCursorVisible()
QApplication.processEvents()
def _process_log_line(self, line_with_delimiter):
"""Обрабатывает одну строку лога, управляя заменой строк прогресса."""
is_progress_line = '\r' in line_with_delimiter
# Фильтруем "мусорные" строки прогресса (например, '-=O=-' от wget),
# обрабатывая только те, что содержат знак процента.
if is_progress_line:
if not re.search(r'\d\s*%', line_with_delimiter):
return # Игнорируем строку прогресса без процентов
clean_line = line_with_delimiter.replace('\r', '').replace('\n', '').strip()
if not clean_line:
return
cursor = self.log_output.textCursor()
# Если новая строка - это прогресс, и предыдущая тоже была прогрессом,
# то мы удаляем старую, чтобы заменить ее новой.
if is_progress_line and self.last_line_was_progress:
cursor.movePosition(QTextCursor.End)
cursor.select(QTextCursor.LineUnderCursor)
cursor.removeSelectedText()
elif not is_progress_line and self.last_line_was_progress:
# Это переход от строки прогресса к финальной строке.
# Вместо добавления переноса, мы заменяем предыдущую строку новой.
cursor.movePosition(QTextCursor.End)
cursor.select(QTextCursor.LineUnderCursor)
cursor.removeSelectedText()
# Добавляем новую очищенную строку.
# Для прогресса - без переноса строки, для обычных строк - с переносом.
self.append_log(clean_line, add_newline=not is_progress_line)
self.last_line_was_progress = is_progress_line
def handle_process_output(self):
"""Обрабатывает вывод процесса"""
output = self.install_process.readAllStandardOutput().data().decode('utf-8', errors='ignore').strip()
if output:
self.append_log(output)
"""Обрабатывает вывод процесса, корректно отображая однострочный прогресс."""
new_data = self.install_process.readAllStandardOutput().data().decode('utf-8', errors='ignore')
self.output_buffer += new_data
while True:
# Ищем ближайший разделитель (\n или \r)
idx_n = self.output_buffer.find('\n')
idx_r = self.output_buffer.find('\r')
if idx_n == -1 and idx_r == -1:
break # Нет полных строк для обработки
split_idx = min(idx for idx in [idx_n, idx_r] if idx != -1)
# Получаем строку, включая разделитель
line = self.output_buffer[:split_idx + 1]
self.output_buffer = self.output_buffer[split_idx + 1:]
self._process_log_line(line)
def handle_process_finished(self, exit_code, exit_status):
"""Обрабатывает завершение процесса"""
# Обрабатываем остаток в буфере, если он есть
if self.output_buffer:
self._process_log_line(self.output_buffer)
# Если последней строкой был прогресс, "завершаем" его переносом строки.
if self.last_line_was_progress:
cursor = self.log_output.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText("\n")
self._reset_log_state()
if exit_code == 0 and exit_status == QProcess.NormalExit:
self.append_log("\n=== Установка успешно завершена ===")
# Создаем кастомный диалог, чтобы кнопка была на русском
success_box = QMessageBox(self.install_dialog)
success_box.setWindowTitle("Успех")
success_box.setText(f"Программа {self.current_script} установлена успешно!")
title_name = self._get_current_app_title()
success_box.setText(f"Программа «{title_name}» установлена успешно!")
success_box.setIcon(QMessageBox.Information)
success_box.addButton("Готово", QMessageBox.AcceptRole)
success_box.exec_()