feat(theme-security): add theme safety checks and unify loading via ThemeManager
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -2,8 +2,10 @@ from PySide6.QtCore import QPropertyAnimation, QByteArray, QEasingCurve, QAbstra
|
|||||||
from PySide6.QtGui import QPainter, QPen, QColor, QConicalGradient, QBrush
|
from PySide6.QtGui import QPainter, QPen, QColor, QConicalGradient, QBrush
|
||||||
from PySide6.QtWidgets import QWidget, QGraphicsOpacityEffect
|
from PySide6.QtWidgets import QWidget, QGraphicsOpacityEffect
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
|
from portprotonqt.config_utils import read_theme_from_config
|
||||||
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -23,7 +25,8 @@ class SafeOpacityEffect(QGraphicsOpacityEffect):
|
|||||||
class GameCardAnimations:
|
class GameCardAnimations:
|
||||||
def __init__(self, game_card, theme=None):
|
def __init__(self, game_card, theme=None):
|
||||||
self.game_card = game_card
|
self.game_card = game_card
|
||||||
self.theme = theme if theme is not None else default_styles
|
self.theme_manager = ThemeManager()
|
||||||
|
self.theme = theme if theme is not None else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
self.thickness_anim: QPropertyAnimation | None = None
|
self.thickness_anim: QPropertyAnimation | None = None
|
||||||
self.gradient_anim: QPropertyAnimation | None = None
|
self.gradient_anim: QPropertyAnimation | None = None
|
||||||
self.scale_anim: QPropertyAnimation | None = None
|
self.scale_anim: QPropertyAnimation | None = None
|
||||||
@@ -232,7 +235,8 @@ class GameCardAnimations:
|
|||||||
class DetailPageAnimations:
|
class DetailPageAnimations:
|
||||||
def __init__(self, main_window, theme=None):
|
def __init__(self, main_window, theme=None):
|
||||||
self.main_window = main_window
|
self.main_window = main_window
|
||||||
self.theme = theme if theme is not None else default_styles
|
self.theme_manager = ThemeManager()
|
||||||
|
self.theme = theme if theme is not None else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
self.animations = main_window._animations if hasattr(main_window, '_animations') else {}
|
self.animations = main_window._animations if hasattr(main_window, '_animations') else {}
|
||||||
|
|
||||||
def animate_detail_page(self, detail_page: QWidget, load_image_and_restore_effect: Callable, cleanup_animation: Callable):
|
def animate_detail_page(self, detail_page: QWidget, load_image_and_restore_effect: Callable, cleanup_animation: Callable):
|
||||||
|
@@ -9,10 +9,9 @@ from PySide6.QtWidgets import (
|
|||||||
from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase, QTimer
|
from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase, QTimer
|
||||||
from icoextract import IconExtractor, IconExtractorError
|
from icoextract import IconExtractor, IconExtractorError
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from portprotonqt.config_utils import get_portproton_location, read_favorite_folders
|
from portprotonqt.config_utils import get_portproton_location, read_favorite_folders, read_theme_from_config
|
||||||
from portprotonqt.localization import _
|
from portprotonqt.localization import _
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
|
||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
from portprotonqt.custom_widgets import AutoSizeButton
|
from portprotonqt.custom_widgets import AutoSizeButton
|
||||||
from portprotonqt.downloader import Downloader
|
from portprotonqt.downloader import Downloader
|
||||||
@@ -94,8 +93,8 @@ class GameLaunchDialog(QDialog):
|
|||||||
"""Modal dialog to indicate game launch progress, similar to Steam's launch dialog."""
|
"""Modal dialog to indicate game launch progress, similar to Steam's launch dialog."""
|
||||||
def __init__(self, parent=None, game_name=None, theme=None, target_exe=None):
|
def __init__(self, parent=None, game_name=None, theme=None, target_exe=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.theme = theme if theme else default_styles
|
|
||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
|
self.theme = theme if theme else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
self.game_name = game_name
|
self.game_name = game_name
|
||||||
self.target_exe = target_exe # Store the target executable name
|
self.target_exe = target_exe # Store the target executable name
|
||||||
self.setWindowTitle(_("Launching {0}").format(self.game_name))
|
self.setWindowTitle(_("Launching {0}").format(self.game_name))
|
||||||
@@ -173,8 +172,8 @@ class GameLaunchDialog(QDialog):
|
|||||||
class FileExplorer(QDialog):
|
class FileExplorer(QDialog):
|
||||||
def __init__(self, parent=None, theme=None, file_filter=None, initial_path=None, directory_only=False):
|
def __init__(self, parent=None, theme=None, file_filter=None, initial_path=None, directory_only=False):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.theme = theme if theme else default_styles
|
|
||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
|
self.theme = theme if theme else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
self.file_signal = FileSelectedSignal()
|
self.file_signal = FileSelectedSignal()
|
||||||
self.file_filter = file_filter # Store the file filter
|
self.file_filter = file_filter # Store the file filter
|
||||||
self.directory_only = directory_only # Store the directory_only flag
|
self.directory_only = directory_only # Store the directory_only flag
|
||||||
@@ -590,8 +589,8 @@ class AddGameDialog(QDialog):
|
|||||||
def __init__(self, parent=None, theme=None, edit_mode=False, game_name=None, exe_path=None, cover_path=None):
|
def __init__(self, parent=None, theme=None, edit_mode=False, game_name=None, exe_path=None, cover_path=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
from portprotonqt.context_menu_manager import CustomLineEdit # Локальный импорт
|
from portprotonqt.context_menu_manager import CustomLineEdit # Локальный импорт
|
||||||
self.theme = theme if theme else default_styles
|
|
||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
|
self.theme = theme if theme else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
self.edit_mode = edit_mode
|
self.edit_mode = edit_mode
|
||||||
self.original_name = game_name
|
self.original_name = game_name
|
||||||
self.last_exe_path = exe_path # Store last selected exe path
|
self.last_exe_path = exe_path # Store last selected exe path
|
||||||
|
@@ -2,12 +2,10 @@ from PySide6.QtGui import QPainter, QColor, QDesktopServices
|
|||||||
from PySide6.QtCore import Signal, Property, Qt, QUrl
|
from PySide6.QtCore import Signal, Property, Qt, QUrl
|
||||||
from PySide6.QtWidgets import QFrame, QGraphicsDropShadowEffect, QVBoxLayout, QWidget, QStackedLayout, QLabel
|
from PySide6.QtWidgets import QFrame, QGraphicsDropShadowEffect, QVBoxLayout, QWidget, QStackedLayout, QLabel
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
|
||||||
from portprotonqt.image_utils import load_pixmap_async, round_corners
|
from portprotonqt.image_utils import load_pixmap_async, round_corners
|
||||||
from portprotonqt.localization import _
|
from portprotonqt.localization import _
|
||||||
from portprotonqt.config_utils import read_favorites, save_favorites, read_display_filter
|
from portprotonqt.config_utils import read_favorites, save_favorites, read_display_filter, read_theme_from_config
|
||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
from portprotonqt.config_utils import read_theme_from_config
|
|
||||||
from portprotonqt.custom_widgets import ClickableLabel
|
from portprotonqt.custom_widgets import ClickableLabel
|
||||||
from portprotonqt.portproton_api import PortProtonAPI
|
from portprotonqt.portproton_api import PortProtonAPI
|
||||||
from portprotonqt.downloader import Downloader
|
from portprotonqt.downloader import Downloader
|
||||||
@@ -56,7 +54,7 @@ class GameCard(QFrame):
|
|||||||
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.customContextMenuRequested.connect(self._show_context_menu)
|
self.customContextMenuRequested.connect(self._show_context_menu)
|
||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
self.theme = theme if theme is not None else default_styles
|
self.theme = theme if theme is not None else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
|
|
||||||
self.display_filter = read_display_filter()
|
self.display_filter = read_display_filter()
|
||||||
self.current_theme_name = read_theme_from_config()
|
self.current_theme_name = read_theme_from_config()
|
||||||
|
@@ -3,7 +3,6 @@ from PySide6.QtGui import QPen, QColor, QPixmap, QPainter, QPainterPath
|
|||||||
from PySide6.QtCore import Qt, QFile, QEvent, QByteArray, QEasingCurve, QPropertyAnimation
|
from PySide6.QtCore import Qt, QFile, QEvent, QByteArray, QEasingCurve, QPropertyAnimation
|
||||||
from PySide6.QtWidgets import QGraphicsItem, QToolButton, QFrame, QLabel, QGraphicsScene, QHBoxLayout, QWidget, QGraphicsView, QVBoxLayout, QSizePolicy
|
from PySide6.QtWidgets import QGraphicsItem, QToolButton, QFrame, QLabel, QGraphicsScene, QHBoxLayout, QWidget, QGraphicsView, QVBoxLayout, QSizePolicy
|
||||||
from PySide6.QtWidgets import QSpacerItem, QGraphicsPixmapItem, QDialog, QApplication
|
from PySide6.QtWidgets import QSpacerItem, QGraphicsPixmapItem, QDialog, QApplication
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
|
||||||
from portprotonqt.config_utils import read_theme_from_config
|
from portprotonqt.config_utils import read_theme_from_config
|
||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
from portprotonqt.downloader import Downloader
|
from portprotonqt.downloader import Downloader
|
||||||
@@ -177,7 +176,8 @@ class FullscreenDialog(QDialog):
|
|||||||
|
|
||||||
self.images = images
|
self.images = images
|
||||||
self.current_index = current_index
|
self.current_index = current_index
|
||||||
self.theme = theme if theme else default_styles
|
self.theme_manager = ThemeManager()
|
||||||
|
self.theme = theme if theme is not None else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
|
|
||||||
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Dialog)
|
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Dialog)
|
||||||
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
@@ -378,7 +378,8 @@ class ImageCarousel(QGraphicsView):
|
|||||||
self.images = images # Список кортежей: (QPixmap, caption)
|
self.images = images # Список кортежей: (QPixmap, caption)
|
||||||
self.image_items = []
|
self.image_items = []
|
||||||
self._animation = None
|
self._animation = None
|
||||||
self.theme = theme if theme else default_styles
|
self.theme_manager = ThemeManager()
|
||||||
|
self.theme = theme if theme is not None else self.theme_manager.apply_theme(read_theme_from_config())
|
||||||
self.max_height = 300 # Default height for images
|
self.max_height = 300 # Default height for images
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self.create_arrows()
|
self.create_arrows()
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
|
import ast
|
||||||
|
import re
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
from PySide6.QtSvg import QSvgRenderer
|
from PySide6.QtSvg import QSvgRenderer
|
||||||
from PySide6.QtGui import QIcon, QColor, QFontDatabase, QPixmap, QPainter
|
from PySide6.QtGui import QIcon, QColor, QFontDatabase, QPixmap, QPainter
|
||||||
|
|
||||||
from portprotonqt.config_utils import save_theme_to_config, load_theme_metainfo
|
from portprotonqt.config_utils import save_theme_to_config, load_theme_metainfo
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -15,6 +16,71 @@ THEMES_DIRS = [
|
|||||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes")
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Запрещенные модули и функции
|
||||||
|
FORBIDDEN_MODULES = {
|
||||||
|
"os",
|
||||||
|
"subprocess",
|
||||||
|
"shutil",
|
||||||
|
"sys",
|
||||||
|
"socket",
|
||||||
|
"ctypes",
|
||||||
|
"pathlib",
|
||||||
|
"glob",
|
||||||
|
}
|
||||||
|
FORBIDDEN_FUNCTIONS = {
|
||||||
|
"exec",
|
||||||
|
"eval",
|
||||||
|
"open",
|
||||||
|
"__import__",
|
||||||
|
}
|
||||||
|
|
||||||
|
FORBIDDEN_PROPERTIES = {
|
||||||
|
"box-shadow",
|
||||||
|
"backdrop-filter",
|
||||||
|
"cursor",
|
||||||
|
"text-shadow",
|
||||||
|
}
|
||||||
|
|
||||||
|
def check_theme_safety(theme_file: str) -> bool:
|
||||||
|
"""
|
||||||
|
Проверяет файл темы на наличие запрещённых модулей и функций.
|
||||||
|
Возвращает True, если файл безопасен, иначе False.
|
||||||
|
"""
|
||||||
|
has_errors = False
|
||||||
|
try:
|
||||||
|
with open(theme_file) as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Проверка на запрещённые QSS-свойства
|
||||||
|
for prop in FORBIDDEN_PROPERTIES:
|
||||||
|
if re.search(rf"{prop}\s*:", content, re.IGNORECASE):
|
||||||
|
logger.error(f"Unknown QSS property found '{prop}' in file {theme_file}")
|
||||||
|
has_errors = True
|
||||||
|
|
||||||
|
# Проверка на опасные импорты и функции
|
||||||
|
try:
|
||||||
|
tree = ast.parse(content)
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
# Проверка импортов
|
||||||
|
if isinstance(node, ast.Import | ast.ImportFrom):
|
||||||
|
for name in node.names:
|
||||||
|
if name.name in FORBIDDEN_MODULES:
|
||||||
|
logger.error(f"Forbidden module '{name.name}' found in file {theme_file}")
|
||||||
|
has_errors = True
|
||||||
|
# Проверка вызовов функций
|
||||||
|
if isinstance(node, ast.Call):
|
||||||
|
if isinstance(node.func, ast.Name) and node.func.id in FORBIDDEN_FUNCTIONS:
|
||||||
|
logger.error(f"Forbidden function '{node.func.id}' found in file {theme_file}")
|
||||||
|
has_errors = True
|
||||||
|
except SyntaxError as e:
|
||||||
|
logger.error(f"Syntax error in file {theme_file}: {e}")
|
||||||
|
has_errors = True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to check theme safety for {theme_file}: {e}")
|
||||||
|
has_errors = True
|
||||||
|
|
||||||
|
return not has_errors
|
||||||
|
|
||||||
def list_themes():
|
def list_themes():
|
||||||
"""
|
"""
|
||||||
Возвращает список доступных тем (названий папок) из каталогов THEMES_DIRS.
|
Возвращает список доступных тем (названий папок) из каталогов THEMES_DIRS.
|
||||||
@@ -66,7 +132,7 @@ def load_theme_fonts(theme_name):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not fonts_folder or not os.path.exists(fonts_folder):
|
if not fonts_folder or not os.path.exists(fonts_folder):
|
||||||
logger.error(f"Папка fonts не найдена для темы '{theme_name}'")
|
logger.error(f"Fonts folder not found for theme '{theme_name}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
for filename in os.listdir(fonts_folder):
|
for filename in os.listdir(fonts_folder):
|
||||||
@@ -75,9 +141,9 @@ def load_theme_fonts(theme_name):
|
|||||||
font_id = QFontDatabase.addApplicationFont(font_path)
|
font_id = QFontDatabase.addApplicationFont(font_path)
|
||||||
if font_id != -1:
|
if font_id != -1:
|
||||||
families = QFontDatabase.applicationFontFamilies(font_id)
|
families = QFontDatabase.applicationFontFamilies(font_id)
|
||||||
logger.info(f"Шрифт {filename} успешно загружен: {families}")
|
logger.info(f"Font {filename} successfully loaded: {families}")
|
||||||
else:
|
else:
|
||||||
logger.error(f"Ошибка загрузки шрифта: {filename}")
|
logger.error(f"Error loading font: {filename}")
|
||||||
|
|
||||||
def load_logo():
|
def load_logo():
|
||||||
logo_path = None
|
logo_path = None
|
||||||
@@ -90,7 +156,7 @@ def load_logo():
|
|||||||
if file_extension == ".svg":
|
if file_extension == ".svg":
|
||||||
renderer = QSvgRenderer(logo_path)
|
renderer = QSvgRenderer(logo_path)
|
||||||
if not renderer.isValid():
|
if not renderer.isValid():
|
||||||
logger.error(f"Ошибка загрузки SVG логотипа: {logo_path}")
|
logger.error(f"Error loading SVG logo: {logo_path}")
|
||||||
return None
|
return None
|
||||||
pixmap = QPixmap(128, 128)
|
pixmap = QPixmap(128, 128)
|
||||||
pixmap.fill(QColor(0, 0, 0, 0))
|
pixmap.fill(QColor(0, 0, 0, 0))
|
||||||
@@ -109,37 +175,42 @@ class ThemeWrapper:
|
|||||||
self.custom_theme = custom_theme
|
self.custom_theme = custom_theme
|
||||||
self.metainfo = metainfo or {}
|
self.metainfo = metainfo or {}
|
||||||
self.screenshots = load_theme_screenshots(self.metainfo.get("name", ""))
|
self.screenshots = load_theme_screenshots(self.metainfo.get("name", ""))
|
||||||
|
self._default_theme = None # Lazy-loaded default theme
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if hasattr(self.custom_theme, name):
|
if hasattr(self.custom_theme, name):
|
||||||
return getattr(self.custom_theme, name)
|
return getattr(self.custom_theme, name)
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
if self._default_theme is None:
|
||||||
return getattr(default_styles, name)
|
self._default_theme = load_theme("standart") # Dynamically load standard theme
|
||||||
|
return getattr(self._default_theme, name)
|
||||||
|
|
||||||
def load_theme(theme_name):
|
def load_theme(theme_name):
|
||||||
"""
|
"""
|
||||||
Динамически загружает модуль стилей выбранной темы и метаинформацию.
|
Динамически загружает модуль стилей выбранной темы и метаинформацию.
|
||||||
Если выбрана стандартная тема, импортируется оригинальный styles.py.
|
Все темы, включая стандартную, проходят проверку безопасности.
|
||||||
Для кастомных тем возвращается обёртка, которая подставляет недостающие атрибуты.
|
Для кастомных тем возвращается обёртка, которая подставляет недостающие атрибуты.
|
||||||
"""
|
"""
|
||||||
if theme_name == "standart":
|
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
|
||||||
return default_styles
|
|
||||||
|
|
||||||
for themes_dir in THEMES_DIRS:
|
for themes_dir in THEMES_DIRS:
|
||||||
theme_folder = os.path.join(themes_dir, theme_name)
|
theme_folder = os.path.join(themes_dir, theme_name)
|
||||||
styles_file = os.path.join(theme_folder, "styles.py")
|
styles_file = os.path.join(theme_folder, "styles.py")
|
||||||
if os.path.exists(styles_file):
|
if os.path.exists(styles_file):
|
||||||
|
# Проверяем безопасность темы перед загрузкой
|
||||||
|
if not check_theme_safety(styles_file):
|
||||||
|
logger.error(f"Theme '{theme_name}' is unsafe, falling back to 'standart'")
|
||||||
|
raise FileNotFoundError(f"Theme '{theme_name}' contains forbidden modules or functions")
|
||||||
|
|
||||||
spec = importlib.util.spec_from_file_location("theme_styles", styles_file)
|
spec = importlib.util.spec_from_file_location("theme_styles", styles_file)
|
||||||
if spec is None or spec.loader is None:
|
if spec is None or spec.loader is None:
|
||||||
continue
|
continue
|
||||||
custom_theme = importlib.util.module_from_spec(spec)
|
custom_theme = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(custom_theme)
|
spec.loader.exec_module(custom_theme)
|
||||||
|
if theme_name == "standart":
|
||||||
|
return custom_theme
|
||||||
meta = load_theme_metainfo(theme_name)
|
meta = load_theme_metainfo(theme_name)
|
||||||
wrapper = ThemeWrapper(custom_theme, metainfo=meta)
|
wrapper = ThemeWrapper(custom_theme, metainfo=meta)
|
||||||
wrapper.screenshots = load_theme_screenshots(theme_name)
|
wrapper.screenshots = load_theme_screenshots(theme_name)
|
||||||
return wrapper
|
return wrapper
|
||||||
raise FileNotFoundError(f"Файл стилей не найден для темы '{theme_name}'")
|
raise FileNotFoundError(f"Styles file not found for theme '{theme_name}'")
|
||||||
|
|
||||||
class ThemeManager:
|
class ThemeManager:
|
||||||
"""
|
"""
|
||||||
@@ -166,12 +237,18 @@ class ThemeManager:
|
|||||||
:param theme_name: Имя темы.
|
:param theme_name: Имя темы.
|
||||||
:return: Загруженный модуль темы (или обёртка).
|
:return: Загруженный модуль темы (или обёртка).
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
theme_module = load_theme(theme_name)
|
theme_module = load_theme(theme_name)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.warning(f"Theme '{theme_name}' not found or unsafe, applying standard theme 'standart'")
|
||||||
|
theme_module = load_theme("standart")
|
||||||
|
theme_name = "standart"
|
||||||
|
save_theme_to_config("standart")
|
||||||
load_theme_fonts(theme_name)
|
load_theme_fonts(theme_name)
|
||||||
self.current_theme_name = theme_name
|
self.current_theme_name = theme_name
|
||||||
self.current_theme_module = theme_module
|
self.current_theme_module = theme_module
|
||||||
save_theme_to_config(theme_name)
|
save_theme_to_config(theme_name)
|
||||||
logger.info(f"Тема '{theme_name}' успешно применена")
|
logger.info(f"Theme '{theme_name}' successfully applied")
|
||||||
return theme_module
|
return theme_module
|
||||||
|
|
||||||
def get_icon(self, icon_name, theme_name=None, as_path=False):
|
def get_icon(self, icon_name, theme_name=None, as_path=False):
|
||||||
@@ -226,7 +303,7 @@ class ThemeManager:
|
|||||||
|
|
||||||
# Если иконка всё равно не найдена
|
# Если иконка всё равно не найдена
|
||||||
if not icon_path or not os.path.exists(icon_path):
|
if not icon_path or not os.path.exists(icon_path):
|
||||||
logger.error(f"Предупреждение: иконка '{icon_name}' не найдена")
|
logger.error(f"Warning: icon '{icon_name}' not found")
|
||||||
return QIcon() if not as_path else None
|
return QIcon() if not as_path else None
|
||||||
|
|
||||||
if as_path:
|
if as_path:
|
||||||
|
@@ -9,7 +9,6 @@ from PySide6.QtGui import QIcon, QAction
|
|||||||
from PySide6.QtCore import QTimer
|
from PySide6.QtCore import QTimer
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
import portprotonqt.themes.standart.styles as default_styles
|
|
||||||
from portprotonqt.localization import _
|
from portprotonqt.localization import _
|
||||||
from portprotonqt.config_utils import read_favorites, read_theme_from_config, save_theme_to_config
|
from portprotonqt.config_utils import read_favorites, read_theme_from_config, save_theme_to_config
|
||||||
from portprotonqt.dialogs import GameLaunchDialog
|
from portprotonqt.dialogs import GameLaunchDialog
|
||||||
@@ -31,15 +30,7 @@ class TrayManager:
|
|||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
selected_theme = read_theme_from_config()
|
selected_theme = read_theme_from_config()
|
||||||
self.current_theme_name = selected_theme
|
self.current_theme_name = selected_theme
|
||||||
try:
|
|
||||||
self.theme = self.theme_manager.apply_theme(selected_theme)
|
self.theme = self.theme_manager.apply_theme(selected_theme)
|
||||||
except FileNotFoundError:
|
|
||||||
logger.warning(f"Тема '{selected_theme}' не найдена, применяется стандартная тема 'standart'")
|
|
||||||
self.theme = self.theme_manager.apply_theme("standart")
|
|
||||||
self.current_theme_name = "standart"
|
|
||||||
save_theme_to_config("standart")
|
|
||||||
if not self.theme:
|
|
||||||
self.theme = default_styles
|
|
||||||
self.main_window = main_window
|
self.main_window = main_window
|
||||||
self.tray_icon = QSystemTrayIcon(self.main_window)
|
self.tray_icon = QSystemTrayIcon(self.main_window)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user