From c7eddb8b53a2bc55629592dfc98515113d68462c Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Wed, 22 Oct 2025 09:25:19 +0600 Subject: [PATCH 1/7] tray icon changed to winehelper-symbolic.svg --- winehelper | 4 ++-- winehelper_gui.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/winehelper b/winehelper index 7f0503f..5d21d25 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 THIRD_PARTY_FILE +export SCRIPT_NAME USER_WORK_PATH RUN_SCRIPT DATA_PATH CHANGELOG_FILE WH_ICON_PATH LICENSE_FILE AGREEMENT THIRD_PARTY_FILE WH_ICON_TRAY SCRIPT_NAME="$(basename "$0")" if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then @@ -17,7 +17,7 @@ if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then RUN_SCRIPT="/usr/bin/$SCRIPT_NAME" DATA_PATH="/usr/share/$SCRIPT_NAME" WH_ICON_PATH="/usr/share/icons/hicolor/scalable/apps/winehelper.svg" - WH_ICON_TRAY=" /usr/share/icons/hicolor/symbolic/apps/winehelper-symbolic.svg" + WH_ICON_TRAY="/usr/share/icons/hicolor/symbolic/apps/winehelper-symbolic.svg" CHANGELOG_FILE="/usr/share/doc/winehelper-$WH_VERSION/CHANGELOG" LICENSE_FILE="/usr/share/doc/winehelper-$WH_VERSION/LICENSE" AGREEMENT="/usr/share/doc/winehelper-$WH_VERSION/LICENSE_AGREEMENT" diff --git a/winehelper_gui.py b/winehelper_gui.py index d6b7859..becb13f 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -26,6 +26,7 @@ class Var: DATA_PATH = os.environ.get("DATA_PATH") CHANGELOG_FILE = os.environ.get("CHANGELOG_FILE") WH_ICON_PATH = os.environ.get("WH_ICON_PATH") + WH_ICON_TRAY = os.environ.get("WH_ICON_TRAY") LICENSE_FILE = os.environ.get("LICENSE_FILE") LICENSE_AGREEMENT_FILE = os.environ.get("AGREEMENT") THIRD_PARTY_FILE = os.environ.get("THIRD_PARTY_FILE") @@ -1745,7 +1746,7 @@ class WineHelperGUI(QMainWindow): self.tray_icon = QSystemTrayIcon(self) - icon_path = Var.WH_ICON_PATH + icon_path = Var.WH_ICON_TRAY if icon_path and os.path.exists(icon_path): pixmap = QPixmap(icon_path) if not pixmap.isNull(): From eb9bef83e20c2144607e84c1e5dd8b96f2e197b3 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Wed, 22 Oct 2025 11:13:55 +0600 Subject: [PATCH 2/7] improved display of test versions --- winehelper_gui.py | 85 +++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index becb13f..4c682b5 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -2202,6 +2202,7 @@ class WineHelperGUI(QMainWindow): self.install_tabs_data['auto'] = { 'buttons': buttons, 'layout': layout, 'search_edit': search_edit, 'scroll_area': scroll_area } + self.install_tabs_data['auto']['test_buttons'] = [] # Добавляем чекбокс для тестовых версий test_checkbox = QCheckBox("Показать тестовые версии") @@ -2237,43 +2238,63 @@ class WineHelperGUI(QMainWindow): if not data: return - script_folders = ["autoinstall"] - if data['test_checkbox'].isChecked(): - script_folders.append("testinstall") + is_checked = data['test_checkbox'].isChecked() + test_buttons = data.get('test_buttons', []) - # Перед удалением кнопок останавливаем все связанные с ними таймеры анимации - for btn in data['buttons']: - if btn in self.icon_animators: - anim_data = self.icon_animators.pop(btn) - if 'main_timer' in anim_data: - anim_data['main_timer'].stop() - if 'animation' in anim_data and anim_data['animation']: - anim_data['animation'].stop() - - # Сбрасываем ссылку на активную кнопку, если она была удалена - if self.current_active_button in data['buttons']: - self.current_active_button = None - - # Очищаем старые кнопки и layout - for btn in data['buttons']: - btn.parent().deleteLater() - data['buttons'].clear() - - # Заполняем layout новыми кнопками - scripts = [] - for folder in script_folders: - script_path = os.path.join(Var.DATA_PATH, folder) + # Если нужно показать тестовые версии и они еще не добавлены + if is_checked and not test_buttons: + test_script_folder = "testinstall" + script_path = os.path.join(Var.DATA_PATH, test_script_folder) if os.path.isdir(script_path): try: folder_scripts = sorted(os.listdir(script_path)) - self._populate_install_grid(data['layout'], folder_scripts, folder, data['buttons']) - scripts.extend(folder_scripts) - except OSError as e: - print(f"Не удалось прочитать директорию {script_path}: {e}") + # Запоминаем, какие кнопки являются тестовыми + new_test_buttons = [] + self._populate_install_grid(data['layout'], folder_scripts, test_script_folder, new_test_buttons) + data['test_buttons'] = new_test_buttons + data['buttons'].extend(new_test_buttons) + self.autoinstall_scripts.extend(folder_scripts) - self.autoinstall_scripts = scripts - # Применяем текущий фильтр поиска к обновленному списку - self.filter_buttons('auto') + # Применяем фильтр и прокручиваем к первому новому элементу + self.filter_buttons('auto') + if new_test_buttons: + first_new_button = new_test_buttons[0] + frame = first_new_button.parent() + if isinstance(frame, QFrame): + # Даем время на отрисовку перед прокруткой + QTimer.singleShot(100, lambda: data['scroll_area'].ensureWidgetVisible(frame, 50, 50)) + + except OSError as e: + print(f"Не удалось прочитать директорию {test_script_folder}: {e}") + + # Если нужно скрыть тестовые версии и они были добавлены + elif not is_checked and test_buttons: + # Останавливаем анимацию и удаляем виджеты тестовых кнопок + for btn in test_buttons: + if btn in self.icon_animators: + anim_data = self.icon_animators.pop(btn) + if 'main_timer' in anim_data: + anim_data['main_timer'].stop() + if 'animation' in anim_data and anim_data['animation']: + anim_data['animation'].stop() + + # Удаляем кнопку из основного списка + if btn in data['buttons']: + data['buttons'].remove(btn) + + # Удаляем фрейм кнопки из layout + frame = btn.parent() + if frame: + frame.deleteLater() + + # Очищаем список тестовых кнопок + data['test_buttons'].clear() + # Обновляем список скриптов + self.autoinstall_scripts = [s for s in self.autoinstall_scripts if not os.path.exists(os.path.join(Var.DATA_PATH, "testinstall", s))] + + # В любом случае применяем фильтр, чтобы скрыть/показать кнопки в соответствии с поиском + if data['test_checkbox'].isChecked(): + self.filter_buttons('auto') def create_installed_tab(self): """Создает вкладку для отображения установленных программ в виде кнопок""" From 16a686dc37f5e421b023aa2f8986231670c7feeb Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 23 Oct 2025 11:30:37 +0600 Subject: [PATCH 3/7] added a new file with general information --- GENERAL | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 GENERAL diff --git a/GENERAL b/GENERAL new file mode 100644 index 0000000..91dbb59 --- /dev/null +++ b/GENERAL @@ -0,0 +1,7 @@ +# Руководство пользователя +Подробное и актуальное руководство по использованию WineHelper смотрите на сайте: https://www.altlinux.org/Winehelper + +# Совместимость ПО и сертификаты +С полным списком совместимого ПО и сертификатами можно ознакомиться по следующим ссылкам: +Для 10 платформы: https://www.basealt.ru/fileadmin/user_upload/compatibility/P10-view2.html +Для 11 платформы: https://www.basealt.ru/fileadmin/user_upload/compatibility/P11-view2.html From 1ad2c6cfa8c1aace48566d5d16ae1a16b236de92 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 23 Oct 2025 11:31:43 +0600 Subject: [PATCH 4/7] The Manual sub-tab has been renamed to General --- winehelper | 4 +++- winehelper_gui.py | 55 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/winehelper b/winehelper index 5d21d25..5f09472 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 THIRD_PARTY_FILE WH_ICON_TRAY +export SCRIPT_NAME USER_WORK_PATH RUN_SCRIPT DATA_PATH CHANGELOG_FILE WH_ICON_PATH LICENSE_FILE AGREEMENT THIRD_PARTY_FILE WH_ICON_TRAY GENERAL SCRIPT_NAME="$(basename "$0")" if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then @@ -22,6 +22,7 @@ if [[ "$(realpath "$0")" == "/usr/bin/$SCRIPT_NAME" ]] ; then LICENSE_FILE="/usr/share/doc/winehelper-$WH_VERSION/LICENSE" AGREEMENT="/usr/share/doc/winehelper-$WH_VERSION/LICENSE_AGREEMENT" THIRD_PARTY_FILE="/usr/share/doc/winehelper-$WH_VERSION/THIRD-PARTY" + GENERAL="/usr/share/doc/winehelper-$WH_VERSION/GENERAL" else # переменные для тестового запуска WineHelper из репозитория USER_WORK_PATH="$HOME/test-$SCRIPT_NAME" @@ -33,6 +34,7 @@ else LICENSE_FILE="$DATA_PATH/LICENSE" AGREEMENT="$DATA_PATH/LICENSE_AGREEMENT" THIRD_PARTY_FILE="$DATA_PATH/THIRD-PARTY" + GENERAL="$DATA_PATH/GENERAL" WH_DEVEL="1" # минимальная проверка синтаксиса скриптов diff --git a/winehelper_gui.py b/winehelper_gui.py index 4c682b5..354c3d2 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -30,6 +30,7 @@ class Var: LICENSE_FILE = os.environ.get("LICENSE_FILE") LICENSE_AGREEMENT_FILE = os.environ.get("AGREEMENT") THIRD_PARTY_FILE = os.environ.get("THIRD_PARTY_FILE") + GENERAL = os.environ.get("GENERAL") class DependencyManager: """Класс для управления проверкой и установкой системных зависимостей.""" @@ -1793,10 +1794,7 @@ class WineHelperGUI(QMainWindow): title = "Автоматическая установка" html_content = ("

Автоматическая установка

" "

Скрипты из этого списка скачают, установят и настроят приложение за вас. Просто выберите программу и нажмите «Установить».

" - "

Для доступа к экспериментальным скриптам установки отметьте опцию «Показать тестовые версии» внизу списка.

" - "

Совместимость с дистрибутивами Альт

" - "

С полным списком совместимого ПО и сертификатами (не только для WineHelper) можно ознакомиться по следующим ссылкам:
" - "Для 10 платформы | Для 11 платформы

") + "

Для доступа к экспериментальным скриптам установки отметьте опцию «Показать тестовые версии» внизу списка.

") show_global = False elif tab_name == "Ручная установка": title = "Ручная установка" @@ -3256,17 +3254,44 @@ class WineHelperGUI(QMainWindow): help_subtabs = QTabWidget() help_layout.addWidget(help_subtabs) - # Подвкладка "Руководство" - guide_tab = QWidget() - guide_layout = QVBoxLayout(guide_tab) - guide_text = QTextBrowser() - guide_text.setOpenExternalLinks(True) - guide_text.setHtml(""" -

Руководство пользователя

-

Подробное и актуальное руководство по использованию WineHelper смотрите на https://www.altlinux.org/Winehelper

- """) - guide_layout.addWidget(guide_text) - help_subtabs.addTab(guide_tab, "Руководство") + # Подвкладка "Общее" + general_tab = QWidget() + general_layout = QVBoxLayout(general_tab) + general_text = QTextBrowser() + general_text.setOpenExternalLinks(True) + + try: + if not Var.GENERAL or not os.path.exists(Var.GENERAL): + raise FileNotFoundError + + with open(Var.GENERAL, 'r', encoding='utf-8') as f: + general_content = f.read() + + html_content = "" + url_re = re.compile(r'(https?://[^\s]+)') + + for line in general_content.splitlines(): + line = line.strip() + if not line: + html_content += "
" + continue + + line = html.escape(line) + line = url_re.sub(r'\1', line) + + if line.startswith('# '): + html_content += f'

{line[2:]}

' + elif line.startswith('Для '): + html_content += f'

• {line}

' + else: + html_content += f'

{line}

' + + general_text.setHtml(html_content) + except (FileNotFoundError, TypeError): + general_text.setHtml(f'

Ошибка

Не удалось загрузить файл с общей информацией по пути:
{Var.GENERAL}

') + + general_layout.addWidget(general_text) + help_subtabs.addTab(general_tab, "Общее") # Подвкладка "Авторы" authors_tab = QWidget() From c68bcc9abf206e2a9650ed98af85457567ae23b1 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 23 Oct 2025 12:38:10 +0600 Subject: [PATCH 5/7] added a method to change the color of the tray icon --- winehelper_gui.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 354c3d2..34fce28 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -13,8 +13,8 @@ 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, QInputDialog, QDialogButtonBox, QSystemTrayIcon, QMenu) -from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve, pyqtSignal -from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor, QTextCharFormat +from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve, pyqtSignal, QRect +from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor, QTextCharFormat, QColor from PyQt5.QtNetwork import QLocalServer, QLocalSocket @@ -1739,6 +1739,18 @@ class WineHelperGUI(QMainWindow): self.raise_() self.activateWindow() + def create_colorized_icon(self, icon_path, color): + """Загружает иконку (SVG) и применяет к ней указанный цвет.""" + target_pixmap = QPixmap(icon_path) + if target_pixmap.isNull(): + return QIcon() + + painter = QPainter(target_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(target_pixmap.rect(), QColor(color)) + painter.end() + return QIcon(target_pixmap) + def create_tray_icon(self): """Создает и настраивает иконку в системном трее.""" if not QSystemTrayIcon.isSystemTrayAvailable(): @@ -1747,11 +1759,8 @@ class WineHelperGUI(QMainWindow): self.tray_icon = QSystemTrayIcon(self) - icon_path = Var.WH_ICON_TRAY - if icon_path and os.path.exists(icon_path): - pixmap = QPixmap(icon_path) - if not pixmap.isNull(): - self.tray_icon.setIcon(QIcon(pixmap)) + if Var.WH_ICON_TRAY and os.path.exists(Var.WH_ICON_TRAY): + self.tray_icon.setIcon(self.create_colorized_icon(Var.WH_ICON_TRAY, "#0078d7")) # Создаем и сохраняем меню как атрибут класса, чтобы оно не удалялось self.tray_menu = QMenu(self) From 5b572ff54082fa797c162ea6219472860b4cfa37 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 23 Oct 2025 14:00:20 +0600 Subject: [PATCH 6/7] added a change in the color of the tray icon depending on the theme --- winehelper_gui.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 34fce28..7cf8eb7 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -14,7 +14,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QH QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox, QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog, QDialogButtonBox, QSystemTrayIcon, QMenu) from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve, pyqtSignal, QRect -from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor, QTextCharFormat, QColor +from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor, QTextCharFormat, QColor, QPalette from PyQt5.QtNetwork import QLocalServer, QLocalSocket @@ -1759,8 +1759,23 @@ class WineHelperGUI(QMainWindow): self.tray_icon = QSystemTrayIcon(self) + # --- Определение цвета иконки в зависимости от темы --- if Var.WH_ICON_TRAY and os.path.exists(Var.WH_ICON_TRAY): - self.tray_icon.setIcon(self.create_colorized_icon(Var.WH_ICON_TRAY, "#0078d7")) + # Получаем цвет текста окна из палитры приложения. + # Это хороший индикатор для определения контрастного цвета. + window_text_color = self.palette().color(QPalette.WindowText) + + # Если цвет текста светлый (высокая яркость), значит, тема темная. + # И наоборот: если цвет текста темный, тема светлая. + # Яркость > 127 обычно указывает на светлый цвет. + is_dark_theme = window_text_color.lightness() > 127 + + if is_dark_theme: + # Для темных тем используем белую иконку + self.tray_icon.setIcon(self.create_colorized_icon(Var.WH_ICON_TRAY, Qt.white)) + else: + # Для светлых тем используем черную иконку + self.tray_icon.setIcon(self.create_colorized_icon(Var.WH_ICON_TRAY, Qt.black)) # Создаем и сохраняем меню как атрибут класса, чтобы оно не удалялось self.tray_menu = QMenu(self) From 5e4d94bb57906e5b747322436dfea61d4871c514 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 23 Oct 2025 14:02:50 +0600 Subject: [PATCH 7/7] temporarily disable the display of the tray icon --- winehelper_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 7cf8eb7..d946ad3 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -4974,7 +4974,7 @@ def main(): window.server = server window.show() # Создаем иконку в системном трее после создания окна - window.create_tray_icon() + # window.create_tray_icon() # Временно отключено return app.exec_() return 1