From c58903846d564929c6e44fa959d71b3645407e44 Mon Sep 17 00:00:00 2001 From: huangzhenpc Date: Wed, 12 Feb 2025 13:33:11 +0800 Subject: [PATCH] refactor: migrate GUI from tkinter to PyQt5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改动: 1. GUI框架迁移 - 从 tkinter 完全迁移到 PyQt5 - 重写了所有界面组件和布局 - 优化了界面交互逻辑 2. 界面改进 - 使用 QMainWindow 作为主窗口 - 采用 QVBoxLayout 和 QHBoxLayout 进行布局 - 使用 QFrame 组织界面结构 - 添加了状态显示区域的滚动功能 3. 功能优化 - 改进了错误处理和消息显示 - 使用 QMessageBox 替代 tkinter messagebox - 添加了剪贴板操作功能 - 优化了状态更新逻辑 4. 性能提升 - 移除了不必要的窗口刷新 - 使用 QTimer 优化状态检查 - 简化了窗口管理逻辑 5. 代码质量 - 改进了代码组织结构 - 增强了错误处理 - 添加了详细的日志记录 - 优化了资源管理 6. 依赖管理 - 更新了 PyInstaller spec 文件 - 添加了必要的 PyQt5 依赖 - 确保打包后的程序正常运行 --- build_nezha.spec | 2 +- gui/main_window.py | 328 ++++++++++++--------------------------------- main.py | 34 ++++- 3 files changed, 118 insertions(+), 246 deletions(-) diff --git a/build_nezha.spec b/build_nezha.spec index de0c009..6cecec4 100644 --- a/build_nezha.spec +++ b/build_nezha.spec @@ -15,7 +15,7 @@ a = Analysis( datas=[('icon', 'icon'), ('version.txt', '.')], hiddenimports=[ 'win32gui', 'win32con', 'win32process', 'psutil', # Windows API 相关 - 'tkinter', 'tkinter.ttk', # GUI 相关 + 'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets', # GUI 相关 'requests', 'urllib3', 'certifi', # 网络请求相关 'json', 'uuid', 'hashlib', 'logging' # 基础功能相关 ], diff --git a/gui/main_window.py b/gui/main_window.py index 1c5ef03..aa509ee 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -1,11 +1,14 @@ import sys -import tkinter as tk -from tkinter import ttk, messagebox from pathlib import Path import logging import os -from PIL import Image, ImageTk -import time +from PIL import Image +from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QLabel, QLineEdit, QPushButton, QFrame, QTextEdit, + QMessageBox, QApplication) +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtGui import QIcon, QPixmap + sys.path.append(str(Path(__file__).parent.parent)) from utils.config import Config @@ -20,149 +23,120 @@ def get_version(): logging.error(f"读取版本号失败: {str(e)}") return "未知版本" -class MainWindow: +class MainWindow(QMainWindow): def __init__(self): + super().__init__() self.config = Config() self.switcher = AccountSwitcher() - self.root = tk.Tk() version = get_version() cursor_version = self.switcher.get_cursor_version() - self.root.title(f"听泉Cursor助手 v{version} (本机Cursor版本: {cursor_version})") - self.root.geometry("600x500") # 调整窗口大小 - self.root.resizable(True, True) # 允许调整窗口大小 - - # 设置窗口最小尺寸 - self.root.minsize(600, 500) + self.setWindowTitle(f"听泉Cursor助手 v{version} (本机Cursor版本: {cursor_version})") + self.setMinimumSize(600, 500) + # 设置图标 try: - # 设置图标 - 使用PIL current_dir = os.path.dirname(os.path.abspath(__file__)) icon_path = os.path.join(os.path.dirname(current_dir), "icon", "th.jpg") if os.path.exists(icon_path): - # 打开并调整图片大小 - img = Image.open(icon_path) - img = img.resize((32, 32), Image.Resampling.LANCZOS) - # 转换为PhotoImage - self.icon = ImageTk.PhotoImage(img) - self.root.iconphoto(True, self.icon) + self.setWindowIcon(QIcon(icon_path)) logging.info(f"成功加载图标: {icon_path}") else: logging.error(f"图标文件不存在: {icon_path}") except Exception as e: logging.error(f"设置图标失败: {str(e)}") - # 设置关闭窗口处理 - self.root.protocol("WM_DELETE_WINDOW", self.on_closing) + # 创建主窗口部件 + central_widget = QWidget() + self.setCentralWidget(central_widget) - # 初始化状态变量 - self.status_var = tk.StringVar(value="") - - # 设置样式 - self.style = ttk.Style() - self.style.configure("TButton", padding=5, font=("Microsoft YaHei UI", 9)) - self.style.configure("TLabelframe", padding=10, font=("Microsoft YaHei UI", 9)) - self.style.configure("TLabel", padding=2, font=("Microsoft YaHei UI", 9)) - self.style.configure("Custom.TButton", padding=10, font=("Microsoft YaHei UI", 9)) - self.style.configure("Action.TButton", padding=8, font=("Microsoft YaHei UI", 9)) - - self.setup_ui() - - # 启动时检查一次状态 - self.check_status() - - def setup_ui(self): - """设置UI界面""" - # 主框架 - main_frame = ttk.Frame(self.root, padding=10) - main_frame.pack(fill="both", expand=True) - - # 功能菜单 - menu_frame = ttk.Frame(main_frame) - menu_frame.pack(fill="x", pady=(0, 10)) - ttk.Label(menu_frame, text="功能(F)").pack(side="left") + # 创建主布局 + main_layout = QVBoxLayout(central_widget) # 设备ID区域 - device_frame = ttk.Frame(main_frame) - device_frame.pack(fill="x", pady=(0, 10)) - - ttk.Label(device_frame, text="设备识别码(勿动):").pack(side="left") - self.hardware_id_var = tk.StringVar(value=self.switcher.hardware_id) - device_id_entry = ttk.Entry(device_frame, textvariable=self.hardware_id_var, width=35, state="readonly") - device_id_entry.pack(side="left", padx=5) - - copy_btn = ttk.Button(device_frame, text="复制ID", command=self.copy_device_id, width=8) - copy_btn.pack(side="left") + device_frame = QFrame() + device_layout = QHBoxLayout(device_frame) + device_layout.addWidget(QLabel("设备识别码(勿动):")) + self.hardware_id_edit = QLineEdit(self.switcher.hardware_id) + self.hardware_id_edit.setReadOnly(True) + device_layout.addWidget(self.hardware_id_edit) + copy_btn = QPushButton("复制ID") + copy_btn.clicked.connect(self.copy_device_id) + device_layout.addWidget(copy_btn) + main_layout.addWidget(device_frame) # 会员状态区域 - status_frame = ttk.LabelFrame(main_frame, text="会员状态") - status_frame.pack(fill="x", pady=(0, 10)) - - self.status_text = tk.Text(status_frame, height=5, width=40, font=("Microsoft YaHei UI", 9)) - self.status_text.pack(fill="both", padx=5, pady=5) - self.status_text.config(state="disabled") + status_frame = QFrame() + status_layout = QVBoxLayout(status_frame) + status_layout.addWidget(QLabel("会员状态")) + self.status_text = QTextEdit() + self.status_text.setReadOnly(True) + self.status_text.setMinimumHeight(100) + status_layout.addWidget(self.status_text) + main_layout.addWidget(status_frame) # 激活区域 - activation_frame = ttk.LabelFrame(main_frame, text="激活(叠加)会员,多个激活码可叠加整体时长") - activation_frame.pack(fill="x", pady=(0, 10)) - - input_frame = ttk.Frame(activation_frame) - input_frame.pack(fill="x", padx=5, pady=5) - - ttk.Label(input_frame, text="激活码:").pack(side="left") - self.activation_var = tk.StringVar() - activation_entry = ttk.Entry(input_frame, textvariable=self.activation_var, width=35) - activation_entry.pack(side="left", padx=5) - - activate_btn = ttk.Button(input_frame, text="激活", command=self.activate_account, width=8) - activate_btn.pack(side="left") + activation_frame = QFrame() + activation_layout = QVBoxLayout(activation_frame) + activation_layout.addWidget(QLabel("激活(叠加)会员,多个激活码可叠加整体时长")) + input_frame = QFrame() + input_layout = QHBoxLayout(input_frame) + input_layout.addWidget(QLabel("激活码:")) + self.activation_edit = QLineEdit() + input_layout.addWidget(self.activation_edit) + activate_btn = QPushButton("激活") + activate_btn.clicked.connect(self.activate_account) + input_layout.addWidget(activate_btn) + activation_layout.addWidget(input_frame) + main_layout.addWidget(activation_frame) # 操作按钮区域 - btn_frame = ttk.Frame(main_frame) - btn_frame.pack(fill="x", pady=5) + btn_frame = QFrame() + btn_layout = QVBoxLayout(btn_frame) + refresh_btn = QPushButton("刷新Cursor编辑器授权") + refresh_btn.clicked.connect(self.refresh_cursor_auth) + btn_layout.addWidget(refresh_btn) + bypass_btn = QPushButton("突破Cursor0.45.x限制") + bypass_btn.clicked.connect(self.dummy_function) + btn_layout.addWidget(bypass_btn) + disable_update_btn = QPushButton("禁用Cursor版本更新") + disable_update_btn.clicked.connect(self.disable_cursor_update) + btn_layout.addWidget(disable_update_btn) + main_layout.addWidget(btn_frame) - self.style.configure("Action.TButton", padding=8) - ttk.Button(btn_frame, text="刷新Cursor编辑器授权", command=self.refresh_cursor_auth, style="Action.TButton").pack(fill="x", pady=2) - ttk.Button(btn_frame, text="突破Cursor0.45.x限制", command=self.dummy_function, style="Action.TButton").pack(fill="x", pady=2) - ttk.Button(btn_frame, text="禁用Cursor版本更新", command=self.disable_cursor_update, style="Action.TButton").pack(fill="x", pady=2) + # 启动时检查一次状态 + QTimer.singleShot(0, self.check_status) def copy_device_id(self): """复制设备ID到剪贴板""" - # 先检查状态 if not self.check_status(): return - self.root.clipboard_clear() - self.root.clipboard_append(self.hardware_id_var.get()) + QApplication.clipboard().setText(self.hardware_id_edit.text()) + QMessageBox.information(self, "提示", "设备ID已复制到剪贴板") def activate_account(self): """激活账号""" - code = self.activation_var.get().strip() + code = self.activation_edit.text().strip() if not code: - messagebox.showwarning("提示", "请输入激活码") + QMessageBox.warning(self, "提示", "请输入激活码") return - self.status_var.set("正在激活...") - self.root.update() - try: success, message, account_info = self.switcher.check_activation_code(code) if success: # 更新会员信息显示 self.update_status_display(account_info) - messagebox.showinfo("激活成功", "激活成功!\n" + message) - self.status_var.set("激活成功") + QMessageBox.information(self, "激活成功", "激活成功!\n" + message) # 清空激活码输入框 - self.activation_var.set("") + self.activation_edit.clear() else: - messagebox.showerror("激活失败", message) - self.status_var.set("激活失败") + QMessageBox.critical(self, "激活失败", message) # 激活后检查一次状态 self.check_status() except Exception as e: - messagebox.showerror("错误", f"激活失败: {str(e)}") - self.status_var.set("发生错误") + QMessageBox.critical(self, "错误", f"激活失败: {str(e)}") # 出错后也检查状态 self.check_status() @@ -175,11 +149,6 @@ class MainWindow: logging.info("激活记录:") for record in status_info['activation_records']: logging.info(f"- 记录: {record}") - - # 启用文本框编辑 - self.status_text.config(state="normal") - # 清空当前内容 - self.status_text.delete(1.0, tk.END) # 更新状态文本 status_map = { @@ -217,25 +186,16 @@ class MainWindow: f"地区:{device_info.get('location', '--')}" ]) - # 写入状态信息 - self.status_text.insert(tk.END, "\n".join(status_lines)) - # 禁用文本框编辑 - self.status_text.config(state="disabled") + # 更新状态文本 + self.status_text.setPlainText("\n".join(status_lines)) def check_status(self): """检查会员状态""" try: - self.status_var.set("正在检查状态...") - self.root.update() - status = self.switcher.get_member_status() if status: self.update_status_display(status) - if status.get('status') == 'inactive': - self.status_var.set("未激活") - return False - self.status_var.set("状态检查完成") - return True + return status.get('status') != 'inactive' else: # 更新为未激活状态 inactive_status = { @@ -247,148 +207,34 @@ class MainWindow: "activation_records": [] } self.update_status_display(inactive_status) - self.status_var.set("未激活") return False except Exception as e: - logging.error(f"检查状态失败: {str(e)}") - self.status_var.set("状态检查失败") - messagebox.showerror("错误", f"检查状态失败: {str(e)}") + QMessageBox.critical(self, "错误", f"检查状态失败: {str(e)}") return False - - def show_purchase_info(self): - """显示购买信息""" - # 创建自定义对话框 - dialog = tk.Toplevel(self.root) - dialog.title("提示") - dialog.geometry("400x280") # 增加高度 - dialog.resizable(False, False) - - # 设置模态 - dialog.transient(self.root) - dialog.grab_set() - - # 创建提示图标 - icon_frame = ttk.Frame(dialog) - icon_frame.pack(pady=10) - ttk.Label(icon_frame, text="ℹ️", font=("Segoe UI", 24)).pack() - - # 创建消息文本 - message_frame = ttk.Frame(dialog) - message_frame.pack(pady=5, padx=20) - ttk.Label(message_frame, text="您还不是会员,请先购买激活会员用讯。", font=("Microsoft YaHei UI", 10)).pack() - - # 创建可复制的文本框 - buy_info = "闲鱼:xxxx\n微信:behikcigar\n网站自助购买:nezhacursor.nosqli.com" - text = tk.Text(dialog, height=4, width=40, font=("Microsoft YaHei UI", 9)) - text.pack(pady=5, padx=20) - text.insert("1.0", buy_info) - text.config(state="normal") # 允许选择和复制 - - # 创建按钮框架 - btn_frame = ttk.Frame(dialog) - btn_frame.pack(pady=10) - - # 复制按钮 - def copy_info(): - dialog.clipboard_clear() - dialog.clipboard_append(buy_info) - messagebox.showinfo("提示", "购买信息已复制到剪贴板", parent=dialog) - ttk.Button(btn_frame, text="复制信息", command=copy_info).pack(side="left", padx=5) - ttk.Button(btn_frame, text="确定", command=dialog.destroy).pack(side="left", padx=5) - - # 设置对话框位置为居中 - dialog.update_idletasks() - width = dialog.winfo_width() - height = dialog.winfo_height() - x = (dialog.winfo_screenwidth() // 2) - (width // 2) - y = (dialog.winfo_screenheight() // 2) - (height // 2) - dialog.geometry(f"{width}x{height}+{x}+{y}") - - # 等待对话框关闭 - self.root.wait_window(dialog) - - def minimize_window(self): - """最小化窗口""" - self.root.iconify() - - def maximize_window(self): - """最大化/还原窗口""" - if self.root.state() == 'zoomed': - self.root.state('normal') - else: - self.root.state('zoomed') - - def on_closing(self): - """窗口关闭处理""" - try: - logging.info("正在关闭程序...") - # 先退出主循环 - self.root.quit() - # 等待一小段时间确保资源释放 - time.sleep(0.5) - except Exception as e: - logging.error(f"关闭程序时出错: {str(e)}") - finally: - try: - # 销毁窗口 - self.root.destroy() - # 再等待一小段时间 - time.sleep(0.5) - # 强制结束进程 - if sys.platform == "win32": - import os - os._exit(0) - except: - # 如果还是无法正常关闭,直接强制退出 - os._exit(0) - - def run(self): - """运行程序""" - self.root.mainloop() - def refresh_cursor_auth(self): - """刷新Cursor编辑器授权""" - if not self.check_status(): - self.show_purchase_info() - return - + """刷新Cursor授权""" try: success, message = self.switcher.refresh_cursor_auth() if success: - messagebox.showinfo("成功", "Cursor编辑器授权刷新成功!\n" + message) + QMessageBox.information(self, "成功", message) else: - messagebox.showerror("错误", message) + QMessageBox.critical(self, "失败", message) except Exception as e: - messagebox.showerror("错误", f"刷新失败: {str(e)}") - - def disable_cursor_update(self): - """禁用Cursor版本更新""" - if not self.check_status(): - self.show_purchase_info() - return + QMessageBox.critical(self, "错误", f"刷新授权失败: {str(e)}") + def disable_cursor_update(self): + """禁用Cursor更新""" try: success, message = self.switcher.disable_cursor_update() if success: - messagebox.showinfo("成功", message) + QMessageBox.information(self, "成功", message) else: - messagebox.showerror("错误", message) + QMessageBox.critical(self, "失败", message) except Exception as e: - messagebox.showerror("错误", f"操作失败: {str(e)}") - - def dummy_function(self): - """突破版本限制""" - if not self.check_status(): - self.show_purchase_info() - return + QMessageBox.critical(self, "错误", f"禁用更新失败: {str(e)}") - try: - success, message = self.switcher.bypass_version_limit() - if success: - messagebox.showinfo("成功", message) - else: - messagebox.showerror("错误", message) - except Exception as e: - messagebox.showerror("错误", f"操作失败: {str(e)}") \ No newline at end of file + def dummy_function(self): + """占位函数""" + QMessageBox.information(self, "提示", "此功能暂未实现") \ No newline at end of file diff --git a/main.py b/main.py index 5c6befb..903996c 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,29 @@ import logging import sys import traceback +import os +import atexit +import shutil +import tempfile from pathlib import Path +from PyQt5.QtWidgets import QApplication, QMessageBox from gui.main_window import MainWindow +def cleanup_temp(): + """清理临时文件""" + try: + temp_dir = Path(tempfile._get_default_tempdir()) + for item in temp_dir.glob('_MEI*'): + try: + if item.is_dir(): + shutil.rmtree(str(item), ignore_errors=True) + elif item.is_file(): + item.unlink() + except: + pass + except: + pass + def setup_logging(): """设置日志""" try: @@ -27,6 +47,9 @@ def setup_logging(): def main(): """主函数""" try: + # 注册退出时的清理函数 + atexit.register(cleanup_temp) + setup_logging() # 检查Python版本 @@ -41,17 +64,20 @@ def main(): logging.info(f" - {p}") logging.info("正在初始化主窗口...") + app = QApplication(sys.argv) window = MainWindow() logging.info("正在启动主窗口...") - window.run() + window.show() + sys.exit(app.exec_()) except Exception as e: error_msg = f"程序运行出错: {str(e)}\n{traceback.format_exc()}" logging.error(error_msg) - # 使用tkinter的消息框显示错误 - from tkinter import messagebox - messagebox.showerror("错误", error_msg) + # 使用 QMessageBox 显示错误 + app = QApplication(sys.argv) + QMessageBox.critical(None, "错误", error_msg) + sys.exit(1) if __name__ == "__main__": main() \ No newline at end of file