Files
HW-Debug-Tools/EDID-viewer/src/theme.py
T
hulk fe4bca5a33 feat(EDID-Viewer): v0.1.0 入库 — Fluent EDID 查看器,目录对齐 DLPilot
- src/ 人写代码:main.py(Fluent 界面、Light/Dark/dark1/dark2/跟随系统主题、
  关于按钮;主题三件套 = setTheme + QPalette/应用级 QSS + DWM 标题栏,
  修复 Light 下窗口背景/页签/标题栏仍为系统深色的问题)、edid.py
  (EDID/CTA/HDMI/HDR 解析)、windows_edid.py(WMI 只读)、theme.py、
  test_edid.py(9 项解析测试,含随机畸形数据无崩溃检查)
- assets/ 图标与生成脚本;release/ 交付:EDID-Viewer.exe(onefile 63.7MB,
  启动/图标/主题已验证)+ README,与 DLPilot/release 同约定 exe 直接入库
- 流水线对齐 DLPilot:src/build_exe.py(spec 由脚本生成不入库,剔除 PATH 上
  Poppler/Conda 版本化 ICU,避免 QtCore 报 WinError 127)→
  package_release.py(组装 EDID-Viewer-v*-win64.zip,zip 只上传 Gitea)→
  publish_gitea.py(token 走 GITEA_TOKEN,自签证书 --insecure)
- agent.md 更名 AGENTS.md(DLPilot 双文档约定:README 给人 / AGENTS.md 给
  Agent);根 .gitignore 增加 EDID-viewer/release/*.zip
- validation/ 保留 EDID 本机/合成样本与窗口截图、关于对话框自检脚本
2026-09-24 22:34:02 +08:00

130 lines
5.5 KiB
Python

"""Theme persistence and Fluent Widgets integration."""
import ctypes
import os
from PySide6.QtCore import QObject, QSettings, Qt
from PySide6.QtGui import QColor, QPalette
from qfluentwidgets import Theme, setTheme, setThemeColor
# Fluent widgets restyle themselves on setTheme(); QMainWindow, the native
# QTabWidget/QStatusBar and the Windows title bar keep the OS palette unless
# the palette, an app stylesheet and the DWM title bar attributes follow suit
# (same approach as DLPilot app_fluent.py).
_SURFACES = {
False: {"window": "#f4f6f8", "base": "#ffffff", "text": "#1f2328",
"dim": "#57606a", "border": "#d8dee4", "selected_text": "#ffffff"},
True: {"window": "#202020", "base": "#2b2b2b", "text": "#f5f5f5",
"dim": "#9a9a9a", "border": "#3a3a3a", "selected_text": "#1f2328"},
}
class ThemeManager(QObject):
"""Keep the appearance choice persistent and synchronized with Qt."""
MODES = {"system", "light", "dark", "dark1", "dark2"}
_THEMES = {
"system": Theme.AUTO,
"light": Theme.LIGHT,
"dark": Theme.DARK,
"dark1": Theme.DARK,
"dark2": Theme.DARK,
}
_ACCENTS = {
"light": "#0f6cbd",
"dark": "#f2c94c",
"dark1": "#5aa9e6",
"dark2": "#35c6c8",
}
def __init__(self, app):
super().__init__(app)
self.app = app
self.window = None
self.settings = QSettings("LT8619C-Tools", "EDID-Viewer")
mode = self.settings.value("appearance/theme", "system")
self.mode = mode if mode in self.MODES else "system"
self.apply()
app.styleHints().colorSchemeChanged.connect(self.apply)
def set_window(self, window):
"""Register the main window so the native title bar follows the theme."""
self.window = window
self._apply_titlebar()
def set_mode(self, mode):
if mode not in self.MODES:
raise ValueError(f"Unknown theme: {mode}")
self.mode = mode
self.settings.setValue("appearance/theme", mode)
self.apply()
def is_dark(self):
if self.mode in {"dark", "dark1", "dark2"}:
return True
return self.mode == "system" and self.app.styleHints().colorScheme() == Qt.ColorScheme.Dark
def apply(self, *_):
is_dark = self.is_dark()
setTheme(self._THEMES[self.mode])
setThemeColor(self._ACCENTS.get(self.mode, self._ACCENTS["light"]))
self._apply_palette(is_dark)
self._apply_titlebar()
def _apply_palette(self, is_dark):
"""Give native Qt surfaces the same colors as the Fluent widgets."""
surface = _SURFACES[is_dark]
accent = QColor(self._ACCENTS.get(self.mode, self._ACCENTS["light"]))
palette = QPalette()
for role, color in (
(QPalette.Window, surface["window"]),
(QPalette.Base, surface["base"]),
(QPalette.AlternateBase, surface["base"]),
(QPalette.Text, surface["text"]),
(QPalette.WindowText, surface["text"]),
(QPalette.Button, surface["base"]),
(QPalette.ButtonText, surface["text"]),
(QPalette.PlaceholderText, surface["dim"]),
(QPalette.Highlight, accent),
(QPalette.HighlightedText, surface["selected_text"]),
(QPalette.ToolTipBase, surface["base"]),
(QPalette.ToolTipText, surface["text"]),
):
palette.setColor(role, QColor(color))
self.app.setPalette(palette)
self.app.setStyleSheet(f"""
QMainWindow, QWidget#root {{ background-color: {surface['window']}; color: {surface['text']}; }}
QTabWidget::pane {{ background: {surface['base']}; border: 1px solid {surface['border']}; border-radius: 6px; }}
QTabBar {{ qproperty-drawBase: 0; }}
QTabBar::tab {{ background: transparent; color: {surface['dim']}; padding: 6px 16px;
border: 1px solid transparent; border-top-left-radius: 6px;
border-top-right-radius: 6px; }}
QTabBar::tab:selected {{ background: {surface['base']}; color: {surface['text']};
border: 1px solid {surface['border']}; }}
QTabBar::tab:hover:!selected {{ background: {surface['base']}; }}
QSplitter::handle {{ background: {surface['window']}; }}
QStatusBar {{ background: {surface['window']}; color: {surface['dim']};
border-top: 1px solid {surface['border']}; }}
QToolTip {{ background-color: {surface['base']}; color: {surface['text']};
border: 1px solid {surface['border']}; }}
""")
def _apply_titlebar(self):
"""Ask DWM for a title bar in the current surface color (Win10/11)."""
if self.window is None or os.name != "nt" or not hasattr(ctypes, "windll"):
return
try:
hwnd = int(self.window.winId())
dwm = ctypes.windll.dwmapi
dark = ctypes.c_int(1 if self.is_dark() else 0)
dwm.DwmSetWindowAttribute(hwnd, 20, ctypes.byref(dark), ctypes.sizeof(dark))
surface = _SURFACES[self.is_dark()]
dwm.DwmSetWindowAttribute(hwnd, 35, ctypes.byref(self._colorref(QColor(surface["window"]))), 4)
dwm.DwmSetWindowAttribute(hwnd, 36, ctypes.byref(self._colorref(QColor(surface["text"]))), 4)
except Exception:
pass
@staticmethod
def _colorref(color):
return ctypes.c_int(color.red() | (color.green() << 8) | (color.blue() << 16))