import os import sys import psutil from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QSystemTrayIcon, QApplication from loguru import logger from PyQt5.QtCore import QSharedMemory, QTimer, QObject, pyqtSignal import darkdetect import datetime as dt from file import base_directory, config_center import signal share = QSharedMemory('ClassWidgets') _stop_in_progress = False def restart(): logger.debug('重启程序') app = QApplication.instance() if app: try: signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL) except (AttributeError, ValueError): pass app.quit() app.processEvents() if share.isAttached(): share.detach() # 释放共享内存 os.execl(sys.executable, sys.executable, *sys.argv) def stop(status=0): global share, update_timer, _stop_in_progress if _stop_in_progress: return _stop_in_progress = True logger.debug('退出程序...') if 'update_timer' in globals() and update_timer: try: update_timer.stop() update_timer = None except Exception as e: logger.warning(f"停止全局更新定时器时出错: {e}") app = QApplication.instance() if app: try: signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL) except (AttributeError, ValueError): pass app.quit() try: current_pid = os.getpid() parent = psutil.Process(current_pid) children = parent.children(recursive=True) if children: logger.debug(f"尝试终止 {len(children)} 个子进程...") for child in children: try: logger.debug(f"终止子进程 {child.pid}...") child.terminate() except psutil.NoSuchProcess: logger.debug(f"子进程 {child.pid} 已不存在.") continue except psutil.AccessDenied: logger.warning(f"无权限终止子进程 {child.pid}.") continue except Exception as e: logger.warning(f"终止子进程 {child.pid} 时出错: {e}") gone, alive = psutil.wait_procs(children, timeout=1.5) if alive: logger.warning(f"{len(alive)} 个子进程未在规定时间内终止,将强制终止...") for p in alive: try: logger.debug(f"强制终止子进程 {p.pid}...") p.kill() except psutil.NoSuchProcess: logger.debug(f"子进程 {p.pid} 在强制终止前已消失.") except Exception as e: logger.error(f"强制终止子进程 {p.pid} 失败: {e}") except psutil.NoSuchProcess: logger.warning("无法获取当前进程信息,跳过子进程终止。") except Exception as e: logger.error(f"终止子进程时出现意外错误: {e}") if 'share' in globals() and share: try: if share.isAttached(): share.detach() logger.debug("共享内存已分离") except Exception as e: logger.error(f"分离共享内存时出错: {e}") logger.debug(f"程序退出({status})") if not app: os._exit(status) def calculate_size(p_w=0.6, p_h=0.7): # 计算尺寸 screen_geometry = QApplication.primaryScreen().geometry() screen_width = screen_geometry.width() screen_height = screen_geometry.height() width = int(screen_width * p_w) height = int(screen_height * p_h) return (width, height), (int(screen_width / 2 - width / 2), 150) def update_tray_tooltip(): """更新托盘文字""" if hasattr(sys.modules[__name__], 'tray_icon'): tray_instance = getattr(sys.modules[__name__], 'tray_icon') if tray_instance is not None: schedule_name_from_conf = config_center.read_conf('General', 'schedule') if schedule_name_from_conf: try: schedule_display_name = schedule_name_from_conf if schedule_display_name.endswith('.json'): schedule_display_name = schedule_display_name[:-5] tray_instance.setToolTip(f'Class Widgets - "{schedule_display_name}"') logger.info(f'托盘文字更新: "Class Widgets - {schedule_display_name}"') except Exception as e: logger.error(f"更新托盘提示时发生错误: {e}") else: tray_instance.setToolTip("Class Widgets - 未加载课表") logger.info(f'托盘文字更新: "Class Widgets - 未加载课表"') class DarkModeWatcher(QObject): darkModeChanged = pyqtSignal(bool) # 发出暗黑模式变化信号 def __init__(self, interval=500, parent=None): super().__init__(parent) self._isDarkMode = darkdetect.isDark() # 初始状态 self._timer = QTimer(self) self._timer.timeout.connect(self._checkTheme) self._timer.start(interval) # 轮询间隔(毫秒) def _checkTheme(self): currentMode = darkdetect.isDark() if currentMode != self._isDarkMode: self._isDarkMode = currentMode self.darkModeChanged.emit(currentMode) # 发出变化信号 def isDark(self): """返回当前是否暗黑模式""" return self._isDarkMode def stop(self): """停止监听""" self._timer.stop() def start(self, interval=None): """开始监听""" if interval: self._timer.setInterval(interval) self._timer.start() class TrayIcon(QSystemTrayIcon): def __init__(self, parent=None): super().__init__(parent) self.setIcon(QIcon(f"{base_directory}/img/logo/favicon.png")) def push_update_notification(self, text=''): self.setIcon(QIcon(f"{base_directory}/img/logo/favicon-update.png")) # tray self.showMessage( "发现 Class Widgets 新版本!", text, QIcon(f"{base_directory}/img/logo/favicon-update.png"), 5000 ) def push_error_notification(self, title='检查更新失败!', text=''): self.setIcon(QIcon(f"{base_directory}/img/logo/favicon-update.png")) # tray self.showMessage( title, text, QIcon(f"{base_directory}/img/logo/favicon-error.ico"), 5000 ) class UnionUpdateTimer(QObject): """ 统一更新计时器 """ def __init__(self, parent=None): super().__init__(parent) self.timer = QTimer(self) self.timer.timeout.connect(self._on_timeout) self.callbacks = [] # 存储所有的回调函数 self._is_running = False def _on_timeout(self): # 超时 app = QApplication.instance() if not app or app.closingDown(): if self.timer.isActive(): self.timer.stop() return # 使用最初的备份列表,防止遍历时修改 callbacks_copy = self.callbacks[:] for callback in callbacks_copy: if callback in self.callbacks: try: callback() except RuntimeError as e: logger.error(f"回调调用错误 (可能对象已删除): {e}") try: self.callbacks.remove(callback) except ValueError: pass except Exception as e: logger.error(f"执行回调时发生未知错误: {e}") if self._is_running: self._schedule_next() def _schedule_next(self): now = dt.datetime.now() next_tick = now.replace(microsecond=0) + dt.timedelta(seconds=1) delay = max(0, int((next_tick - now).total_seconds() * 1000)) self.timer.start(delay) def add_callback(self, callback): if callback not in self.callbacks: self.callbacks.append(callback) if not self._is_running: self.start() def remove_callback(self, callback): try: self.callbacks.remove(callback) except ValueError: pass # if not self.callbacks and self._is_running: # self.stop() # 删除定时器 def remove_all_callbacks(self): self.callbacks = [] # self.stop() # 删除定时器 def start(self): if not self._is_running: logger.debug("启动 UnionUpdateTimer...") self._is_running = True self._schedule_next() def stop(self): self._is_running = False if self.timer: try: if self.timer.isActive(): self.timer.stop() except RuntimeError as e: logger.warning(f"停止 QTimer 时发生运行时错误: {e}") except Exception as e: logger.error(f"停止 QTimer 时发生未知错误: {e}") tray_icon = None update_timer = UnionUpdateTimer()