| import os import sys import time import configparser import win32api import win32con from datetime import datetime, timedelta from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,                             QGroupBox, QRadioButton, QDateTimeEdit, QLabel, QPushButton,                             QCheckBox, QSystemTrayIcon, QMenu, QMessageBox, QSpacerItem,                             QSizePolicy, QFrame) from PyQt5.QtCore import Qt, QTimer, QDateTime, QTime, QSize, QSharedMemory from PyQt5.QtGui import QIcon, QFont, QPalette, QColor   def resource_path(relative_path):     """ 获取资源的绝对路径,适用于开发环境和PyInstaller单文件模式 """     if hasattr(sys, '_MEIPASS'):         # PyInstaller创建的临时文件夹         return os.path.join(sys._MEIPASS, relative_path)     return os.path.join(os.path.abspath('.'), relative_path)   class ShutdownApp(QMainWindow):     def __init__(self):         super().__init__()         self.task_running = False         self.config_file = os.path.join(os.getenv('APPDATA'), 'shutdown_config.ini')         self.first_show = True  # 用于跟踪是否是第一次显示                   self.setup_ui_style()         self.init_ui()         self.load_config()                   # 系统托盘         self.tray_icon = QSystemTrayIcon(self)         self.tray_icon.setIcon(QIcon(resource_path("icon.ico")))         self.tray_icon.setToolTip("定时关机")         self.tray_icon.activated.connect(self.tray_icon_activated)                   # 托盘菜单         self.tray_menu = QMenu()         self.show_action = self.tray_menu.addAction("显示")         self.exit_action = self.tray_menu.addAction("退出")         self.show_action.triggered.connect(self.show_normal)         self.exit_action.triggered.connect(self.confirm_exit)         self.tray_icon.setContextMenu(self.tray_menu)         self.tray_icon.show()  # 确保托盘图标显示                   # 显示当前时间         self.timer = QTimer(self)         self.timer.timeout.connect(self.update_current_time)         self.timer.start(1000)                   # 剩余时间计时器         self.countdown_timer = QTimer(self)         self.countdown_timer.timeout.connect(self.update_remaining_time)           def setup_ui_style(self):         """设置全局UI样式"""         self.setStyleSheet("""             QMainWindow {                 background-color: #f5f5f5;             }             QGroupBox {                 border: 1px solid #ccc;                 border-radius: 4px;                 margin-top: 10px;                 padding-top: 15px;                 font-weight: bold;                 color: #333;             }             QGroupBox::title {                 subcontrol-origin: margin;                 left: 10px;                 padding: 0 3px;             }             QRadioButton, QCheckBox {                 color: #444;             }             QPushButton {                 background-color: #4CAF50;                 color: white;                 border: none;                 padding: 8px 16px;                 border-radius: 4px;                 min-width: 80px;             }             QPushButton:hover {                 background-color: #45a049;             }             QPushButton:disabled {                 background-color: #cccccc;             }             QPushButton#cancel_btn {                 background-color: #f44336;             }             QPushButton#cancel_btn:hover {                 background-color: #d32f2f;             }             QDateTimeEdit {                 padding: 5px;                 border: 1px solid #ddd;                 border-radius: 4px;             }             QLabel#current_time_label {                 font-size: 16px;                 color: #333;                 padding: 5px;                 background-color: #e9f5e9;                 border-radius: 4px;             }             QLabel#remaining_time_label {                 font-size: 16px;                 color: #d32f2f;                 font-weight: bold;                 padding: 5px;                 background-color: #f9e9e9;                 border-radius: 4px;             }         """)           def init_ui(self):         self.setWindowTitle("定时关机")         self.setWindowIcon(QIcon(resource_path("icon.ico")))         self.resize(300, 440)                   # 主窗口布局         main_widget = QWidget()         self.setCentralWidget(main_widget)         layout = QVBoxLayout(main_widget)         layout.setContentsMargins(12, 12, 12, 12)         layout.setSpacing(10)                   # 当前时间显示         self.current_time_label = QLabel()         self.current_time_label.setAlignment(Qt.AlignCenter)         self.current_time_label.setObjectName("current_time_label")         layout.addWidget(self.current_time_label)                   # 添加分隔线         line = QFrame()         line.setFrameShape(QFrame.HLine)         line.setFrameShadow(QFrame.Sunken)         layout.addWidget(line)                   # 定时/延时选择         time_type_group = QGroupBox("时间类型")         time_type_layout = QHBoxLayout()         time_type_layout.setContentsMargins(10, 15, 10, 10)                   self.fixed_time_radio = QRadioButton("定时关机")         self.delay_time_radio = QRadioButton("延时关机")         self.fixed_time_radio.setChecked(True)                   time_type_layout.addWidget(self.fixed_time_radio)         time_type_layout.addWidget(self.delay_time_radio)         time_type_group.setLayout(time_type_layout)         layout.addWidget(time_type_group)                   # 定时时间选择         self.fixed_datetime = QDateTimeEdit()         self.fixed_datetime.setDisplayFormat("yyyy-MM-dd HH:mm:ss")         self.fixed_datetime.setDateTime(QDateTime.currentDateTime())         self.fixed_datetime.setCalendarPopup(True)         layout.addWidget(self.fixed_datetime)                   # 延时时间选择         self.delay_datetime = QDateTimeEdit()         self.delay_datetime.setDisplayFormat("HH:mm:ss")         self.delay_datetime.setTime(QTime(0, 0, 0))         self.delay_datetime.setVisible(False)         layout.addWidget(self.delay_datetime)                   # 连接信号         self.fixed_time_radio.toggled.connect(self.on_time_type_changed)                   # 操作类型         action_group = QGroupBox("操作类型")         action_layout = QHBoxLayout()         action_layout.setContentsMargins(10, 15, 10, 10)                   self.shutdown_radio = QRadioButton("关机")         self.restart_radio = QRadioButton("重启")         self.logoff_radio = QRadioButton("注销")         self.shutdown_radio.setChecked(True)                   action_layout.addWidget(self.shutdown_radio)         action_layout.addWidget(self.restart_radio)         action_layout.addWidget(self.logoff_radio)         action_group.setLayout(action_layout)         layout.addWidget(action_group)                   # 选项         options_group = QGroupBox("选项")         options_layout = QVBoxLayout()         options_layout.setContentsMargins(10, 15, 10, 10)                   self.auto_start_check = QCheckBox("随系统启动")         self.loop_exec_check = QCheckBox("循环执行")                   options_layout.addWidget(self.auto_start_check)         options_layout.addWidget(self.loop_exec_check)         options_group.setLayout(options_layout)         layout.addWidget(options_group)                   # 剩余时间显示         self.remaining_time_label = QLabel()         self.remaining_time_label.setAlignment(Qt.AlignCenter)         self.remaining_time_label.setObjectName("remaining_time_label")         layout.addWidget(self.remaining_time_label)                   # 按钮布局         button_layout = QHBoxLayout()         button_layout.setContentsMargins(0, 10, 0, 0)         button_layout.setSpacing(15)                   self.save_btn = QPushButton("保存设置")         self.cancel_btn = QPushButton("取消")         self.cancel_btn.setObjectName("cancel_btn")         self.cancel_btn.setEnabled(False)                   self.save_btn.clicked.connect(self.save_config)         self.cancel_btn.clicked.connect(self.cancel_task)                   button_layout.addWidget(self.save_btn)         button_layout.addWidget(self.cancel_btn)         layout.addLayout(button_layout)                   # 添加弹簧使布局更紧凑         layout.addSpacerItem(QSpacerItem(20, 10, QSizePolicy.Minimum, QSizePolicy.Expanding))           def on_time_type_changed(self, checked):         self.fixed_datetime.setVisible(checked)         self.delay_datetime.setVisible(not checked)               def update_current_time(self):         current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")         self.current_time_label.setText(f"当前时间: {current_time}")               def save_config(self):         config = configparser.ConfigParser()         config['task'] = {             'time_type': 'fixed' if self.fixed_time_radio.isChecked() else 'delay',             'time': self.fixed_datetime.dateTime().toString("HHmmss") if self.fixed_time_radio.isChecked() else self.delay_datetime.time().toString("HHmmss"),             'execute_type': 'shutdown' if self.shutdown_radio.isChecked() else 'restart' if self.restart_radio.isChecked() else 'logoff',             'auto_start': '1' if self.auto_start_check.isChecked() else '0',             'task_circ': '1' if self.loop_exec_check.isChecked() else '0'         }                   with open(self.config_file, 'w') as f:             config.write(f)                       self.set_auto_start(self.auto_start_check.isChecked())         self.cancel_btn.setEnabled(True)  # 保存后启用取消按钮         self.start_task()               def load_config(self):         if not os.path.exists(self.config_file):             return                       config = configparser.ConfigParser()         config.read(self.config_file)                   if 'task' in config:             task_config = config['task']                           # 时间类型             if task_config.get('time_type', 'fixed') == 'fixed':                 self.fixed_time_radio.setChecked(True)                 time_str = task_config.get('time', '000000')                 qtime = QTime.fromString(time_str, "HHmmss")                 current_date = QDateTime.currentDateTime()                 self.fixed_datetime.setDateTime(QDateTime(current_date.date(), qtime))             else:                 self.delay_time_radio.setChecked(True)                 time_str = task_config.get('time', '000000')                 qtime = QTime.fromString(time_str, "HHmmss")                 self.delay_datetime.setTime(qtime)                               # 操作类型             execute_type = task_config.get('execute_type', 'shutdown')             if execute_type == 'shutdown':                 self.shutdown_radio.setChecked(True)             elif execute_type == 'restart':                 self.restart_radio.setChecked(True)             else:                 self.logoff_radio.setChecked(True)                               # 选项             self.auto_start_check.setChecked(task_config.get('auto_start', '0') == '1')             self.loop_exec_check.setChecked(task_config.get('task_circ', '0') == '1')                           if self.loop_exec_check.isChecked():                 self.cancel_btn.setEnabled(True)                 self.start_task()                       def set_auto_start(self, enable):         key = win32con.HKEY_CURRENT_USER         sub_key = r"SoftwareMicrosoftWindowsCurrentVersionRun"                   try:             reg_key = win32api.RegOpenKey(key, sub_key, 0, win32con.KEY_ALL_ACCESS)             if enable:                 win32api.RegSetValueEx(reg_key, "定时关机", 0, win32con.REG_SZ, sys.executable)             else:                 try:                     win32api.RegDeleteValue(reg_key, "定时关机")                 except:                     pass             win32api.RegCloseKey(reg_key)         except Exception as e:             QMessageBox.warning(self, "警告", f"设置自启动失败: {str(e)}")                   def start_task(self):         if self.task_running:             return                       self.task_running = True         self.toggle_components(True)                   if self.fixed_time_radio.isChecked():             target_time = self.fixed_datetime.dateTime().toPyDateTime()             now = datetime.now()                           if target_time < now:                 target_time += timedelta(days=1)                               self.target_time = target_time         else:             delay = self.delay_datetime.time()             self.target_time = datetime.now() + timedelta(                 hours=delay.hour(),                 minutes=delay.minute(),                 seconds=delay.second()             )                       self.countdown_timer.start(1000)         self.update_remaining_time()               def update_remaining_time(self):         now = datetime.now()         remaining = self.target_time - now                   if remaining.total_seconds() <= 0:             self.execute_action()             if self.loop_exec_check.isChecked():                 self.start_task()             else:                 self.cancel_task()             return                       hours, remainder = divmod(int(remaining.total_seconds()), 3600)         minutes, seconds = divmod(remainder, 60)         remaining_str = f"{hours}小时{minutes}分{seconds}秒"         self.remaining_time_label.setText(f"剩余时间: {remaining_str}")                   # 更新托盘提示         self.tray_icon.setToolTip(f"定时关机 剩余时间: {remaining_str}")               def execute_action(self):         if self.shutdown_radio.isChecked():             os.system("shutdown -s -t 0")         elif self.restart_radio.isChecked():             os.system("shutdown -r -t 0")         else:             os.system("shutdown -l")                   def cancel_task(self):         """取消定时任务"""         if not self.task_running:             QMessageBox.warning(self, "警告", "没有正在运行的任务")             return                       reply = QMessageBox.question(             self,             "确认",             "确定要取消定时任务吗?",             QMessageBox.Yes | QMessageBox.No         )                   if reply == QMessageBox.Yes:             self.task_running = False             self.countdown_timer.stop()             self.remaining_time_label.setText("")             self.tray_icon.setToolTip("定时关机")             self.toggle_components(False)             self.cancel_btn.setEnabled(False)                           # 显示取消成功的提示             QMessageBox.information(self, "提示", "定时任务已取消", QMessageBox.Ok)               def toggle_components(self, disabled):         self.fixed_time_radio.setDisabled(disabled)         self.delay_time_radio.setDisabled(disabled)         self.fixed_datetime.setDisabled(disabled)         self.delay_datetime.setDisabled(disabled)         self.shutdown_radio.setDisabled(disabled)         self.restart_radio.setDisabled(disabled)         self.logoff_radio.setDisabled(disabled)         self.auto_start_check.setDisabled(disabled)         self.loop_exec_check.setDisabled(disabled)         self.save_btn.setDisabled(disabled)               def tray_icon_activated(self, reason):         if reason == QSystemTrayIcon.DoubleClick:             self.show_normal()                   def show_normal(self):         self.show()         self.setWindowState(self.windowState() & ~Qt.WindowMinimized)         self.activateWindow()         self.raise_()               def closeEvent(self, event):         """重写关闭事件,改为最小化到托盘"""         if self.isVisible():             self.hide()             self.tray_icon.show()  # 确保托盘图标显示             if not self.first_show:  # 第一次启动时不显示消息                 self.tray_icon.showMessage(                     "定时关机",                     "程序已最小化到托盘",                     QSystemTrayIcon.Information,                     2000                 )             self.first_show = False             event.ignore()         else:             # 真正退出程序             self.tray_icon.hide()             event.accept()                   def confirm_exit(self):         reply = QMessageBox.question(self, '确认', '是否退出?',                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)         if reply == QMessageBox.Yes:             self.tray_icon.hide()             QApplication.quit()   def main():     # 防止重复运行     shared = QSharedMemory("定时关机")     if not shared.create(512, QSharedMemory.ReadWrite):         QMessageBox.warning(None, "警告", "程序已经在运行!")         sys.exit(0)               # 设置高DPI支持     QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)           app = QApplication(sys.argv)     window = ShutdownApp()           # 如果配置了随系统启动且循环执行,则不显示窗口     config_file = os.path.join(os.getenv('APPDATA'), 'shutdown_config.ini')     if os.path.exists(config_file):         config = configparser.ConfigParser()         config.read(config_file)                   if 'task' in config and config['task'].get('auto_start', '0') == '1':             if config['task'].get('task_circ', '0') == '1':                 window.showMinimized()             else:                 window.show()         else:             window.show()     else:         window.show()               sys.exit(app.exec_())   if __name__ == '__main__':     main() |