From 79e5b43498742e51754426f979ef420746a56681 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Sun, 31 May 2026 18:58:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(theme):=20=E4=BF=AE=E5=A4=8D=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - removeTheme() 删除当前活动主题后从 ConfigManager 读取已保存的主题偏好作为回退方案,不再硬编码 'system' - saveAndApply() 在调用 _applyCustomTheme 前先 setActiveStyle(style),确保主题应用时使用最新选择的内置样式 - _applyCustomTheme() 返回 bool 表示成败,失败时调用方清除配置中的 custom_theme 避免下次启动循环失败 - importTheme() 增加 self.__lock 保护,消除 TOCTOU 竞态条件 - ThemeManager 新增 CfgKey 导入以支持 removeTheme 读取配置 Co-Authored-By: Claude Opus 4.8 --- src/gui/ALSettingsWidget.py | 11 +++-- src/managers/theme/ThemeManager.py | 65 ++++++++++++++++-------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/gui/ALSettingsWidget.py b/src/gui/ALSettingsWidget.py index ab11c84..4450218 100644 --- a/src/gui/ALSettingsWidget.py +++ b/src/gui/ALSettingsWidget.py @@ -34,6 +34,7 @@ import managers.config.ConfigManager as ConfigManager from managers.log.LogManager import instance as logInstance from managers.theme.ThemeManager import( getActiveStyle, + setActiveStyle, instance as themeInstance ) @@ -47,18 +48,20 @@ from interfaces.ConfigProvider import ( def _applyCustomTheme( name: str, fallback_theme: str = "system" -): +) -> bool: if not name: themeInstance().clearTheme(fallback_theme) - return + return True try: themeInstance().applyTheme(name) + return True except Exception as e: logInstance().getLogger("ALSettingsWidget").warning( f"无法应用自定义主题 '{name}',回退到 {fallback_theme} 外观: {e}" ) themeInstance().clearTheme(fallback_theme) + return False def _themeToReadable( need_theme: str @@ -266,7 +269,9 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget): theme, style, custom_theme = self.collectSettings() self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style) self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme) - _applyCustomTheme(custom_theme, theme) + setActiveStyle(style) + if not _applyCustomTheme(custom_theme, theme): + self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "") self.syncRadioFromNeedTheme(custom_theme) # Re-read theme after syncRadioFromNeedTheme — the radio may have # changed to match the custom theme's need_theme diff --git a/src/managers/theme/ThemeManager.py b/src/managers/theme/ThemeManager.py index 412e402..3e77346 100644 --- a/src/managers/theme/ThemeManager.py +++ b/src/managers/theme/ThemeManager.py @@ -19,6 +19,7 @@ from PySide6.QtWidgets import ( QStyleFactory ) +from interfaces.ConfigProvider import CfgKey from managers.config.ConfigManager import instance as configInstance from managers.log.LogManager import instance as logInstance from utils.ThemeUtils import ( @@ -119,35 +120,36 @@ class ThemeManager: if not os.path.isfile(source_path): raise FileNotFoundError(source_path) ext = os.path.splitext(source_path)[1].lower() - if ext == ".qss": - name = os.path.splitext(os.path.basename(source_path))[0] - dest_path = os.path.join(self.__themes_dir, name + ".altheme") - if os.path.exists(dest_path): - raise ValueError(f"主题 '{name}' 已存在") - wrapQssToAtheme(source_path, dest_path, "both") - return name - elif ext == ".altheme": - with zipfile.ZipFile(source_path, "r") as zf: - if "theme.qss" not in zf.namelist(): - raise ValueError("无效的 .altheme: 缺少 theme.qss") - info = readThemeInfo(source_path) - name = info.get("name", os.path.splitext(os.path.basename(source_path))[0]) - safe_name = os.path.basename(name) - dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme") - if os.path.exists(dest_path): - raise ValueError(f"主题 '{safe_name}' 已存在") - # Check for name collision with existing themes by the same author - new_author = info.get("author", "") - for existing in self.listThemes(): - if (existing.get("name", "") == safe_name - and existing.get("author", "") == new_author): - raise ValueError( - f"主题名称 '{safe_name}' (作者 '{new_author}') 已存在" - ) - shutil.copy2(source_path, dest_path) - return safe_name - else: - raise ValueError(f"不支持的文件类型: {ext}") + with self.__lock: + if ext == ".qss": + name = os.path.splitext(os.path.basename(source_path))[0] + dest_path = os.path.join(self.__themes_dir, name + ".altheme") + if os.path.exists(dest_path): + raise ValueError(f"主题 '{name}' 已存在") + wrapQssToAtheme(source_path, dest_path, "both") + return name + elif ext == ".altheme": + with zipfile.ZipFile(source_path, "r") as zf: + if "theme.qss" not in zf.namelist(): + raise ValueError("无效的 .altheme: 缺少 theme.qss") + info = readThemeInfo(source_path) + name = info.get("name", os.path.splitext(os.path.basename(source_path))[0]) + safe_name = os.path.basename(name) + dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme") + if os.path.exists(dest_path): + raise ValueError(f"主题 '{safe_name}' 已存在") + # Check for name collision with existing themes by the same author + new_author = info.get("author", "") + for existing in self.listThemes(): + if (existing.get("name", "") == safe_name + and existing.get("author", "") == new_author): + raise ValueError( + f"主题名称 '{safe_name}' (作者 '{new_author}') 已存在" + ) + shutil.copy2(source_path, dest_path) + return safe_name + else: + raise ValueError(f"不支持的文件类型: {ext}") def listThemes( self @@ -214,7 +216,10 @@ class ThemeManager: os.remove(filepath) if self.__current_theme_name == name: self.__current_theme_name = "" - self.clearTheme("system") + saved_theme = configInstance().get( + CfgKey.GLOBAL.APPEARANCE.THEME, "system" + ) + self.clearTheme(saved_theme) def applyTheme( self,