#!/usr/bin/env python # -*- coding: utf-8 -*- """ 图标管理器 负责图标的加载、缓存和管理 """ import os import glob import customtkinter as ctk from PIL import Image from functools import lru_cache from collections import OrderedDict from config.constants import APP_ICON_MAPPING class IconManager: """图标管理器,负责图标的加载、缓存和管理""" def __init__(self, icons_dir: str, icon_size: int): """ 初始化图标管理器 Args: icons_dir: 图标目录路径 icon_size: 图标大小 """ self.icons_dir = icons_dir self.icon_size = icon_size # 使用有界缓存控制内存占用 self._max_cache_size = 128 self.cache: OrderedDict[str, ctk.CTkImage] = OrderedDict() def get_app_icon(self, app_path: str, config_manager) -> ctk.CTkImage: """ 获取应用图标 Args: app_path: 应用路径 config_manager: 配置管理器 Returns: CTkImage 对象,如果失败则返回 None """ # 检查缓存 if app_path in self.cache: self.cache.move_to_end(app_path) return self.cache[app_path] try: # 查找图标路径 icon_path = self._find_icon_path(app_path, config_manager) if not icon_path: return self._get_fallback_icon(app_path) # 创建并缓存图标 ctk_image = self._create_ctk_icon(icon_path) self.cache[app_path] = ctk_image # 控制缓存大小 if len(self.cache) > self._max_cache_size: self.cache.popitem(last=False) return ctk_image except Exception as e: print(f"Failed to load icon ({app_path}): {e}") return self._get_fallback_icon(app_path) def _find_icon_path(self, app_path: str, config_manager) -> str: """ 查找应用图标路径 查找优先级: 1. 自定义图标 2. 预设图标映射 3. 应用名称匹配 4. 默认图标 5. 任意可用图标 Args: app_path: 应用路径 config_manager: 配置管理器 Returns: 图标文件路径,如果未找到则返回 None """ app_name = os.path.splitext(os.path.basename(app_path))[0] # 1. 检查自定义图标 custom_icon = config_manager.get_app_icon(app_path) if custom_icon and os.path.exists(custom_icon): return custom_icon # 2. 匹配预设图标 app_name_lower = app_name.lower() for key, icon_name in APP_ICON_MAPPING.items(): if key in app_name_lower: icon_path = os.path.join(self.icons_dir, f"{icon_name}.png") if os.path.exists(icon_path): return icon_path # 3. 使用应用名称 icon_path = os.path.join(self.icons_dir, f"{app_name}.png") if os.path.exists(icon_path): return icon_path # 4. 使用默认图标 default_icon = os.path.join(self.icons_dir, "NexusLauncher.ico") if os.path.exists(default_icon): return default_icon # 5. 使用任意可用图标 icons = glob.glob(os.path.join(self.icons_dir, "*.png")) icons.extend(glob.glob(os.path.join(self.icons_dir, "*.ico"))) return icons[0] if icons else None @lru_cache(maxsize=128) def _load_pil_image(self, icon_path: str) -> Image.Image: """ 加载 PIL 图像(带 LRU 缓存) 使用 LRU 缓存可以避免重复加载相同的图标文件, 提升性能并减少磁盘 I/O Args: icon_path: 图标文件路径 Returns: PIL Image 对象 """ return Image.open(icon_path) def _create_ctk_icon(self, icon_path: str) -> ctk.CTkImage: """ 创建 CTk 图标对象 Args: icon_path: 图标文件路径 Returns: CTkImage 对象 """ pil_image = self._load_pil_image(icon_path) icon_display_size = int(self.icon_size * 0.6) return ctk.CTkImage( light_image=pil_image, dark_image=pil_image, size=(icon_display_size, icon_display_size) ) def _get_fallback_icon(self, app_path: str) -> ctk.CTkImage: """ 获取降级图标 当无法加载指定图标时,尝试使用任意可用图标 Args: app_path: 应用路径 Returns: CTkImage 对象,如果失败则返回 None """ try: icons = glob.glob(os.path.join(self.icons_dir, "*.png")) if icons: ctk_image = self._create_ctk_icon(icons[0]) self.cache[app_path] = ctk_image if len(self.cache) > self._max_cache_size: self.cache.popitem(last=False) return ctk_image except: pass return None def clear_cache(self): """清空所有缓存""" self.cache.clear() self._load_pil_image.cache_clear() def update_icon_size(self, new_size: int): """ 更新图标大小 更新图标大小后会清空缓存, 下次获取图标时会使用新的尺寸重新创建 Args: new_size: 新的图标大小 """ self.icon_size = new_size self.clear_cache() def get_cache_info(self) -> dict: """ 获取缓存信息 Returns: 包含缓存统计信息的字典 """ return { 'ctk_cache_size': len(self.cache), 'pil_cache_info': self._load_pil_image.cache_info()._asdict() }