refactor: migrate GUI from tkinter to PyQt5

主要改动:

1. GUI框架迁移 - 从 tkinter 完全迁移到 PyQt5 - 重写了所有界面组件和布局 - 优化了界面交互逻辑

2. 界面改进 - 使用 QMainWindow 作为主窗口 - 采用 QVBoxLayout 和 QHBoxLayout 进行布局 - 使用 QFrame 组织界面结构 - 添加了状态显示区域的滚动功能

3. 功能优化 - 改进了错误处理和消息显示 - 使用 QMessageBox 替代 tkinter messagebox - 添加了剪贴板操作功能 - 优化了状态更新逻辑

4. 性能提升 - 移除了不必要的窗口刷新 - 使用 QTimer 优化状态检查 - 简化了窗口管理逻辑

5. 代码质量 - 改进了代码组织结构 - 增强了错误处理 - 添加了详细的日志记录 - 优化了资源管理

6. 依赖管理 - 更新了 PyInstaller spec 文件 - 添加了必要的 PyQt5 依赖 - 确保打包后的程序正常运行
This commit is contained in:
huangzhenpc
2025-02-12 13:33:11 +08:00
parent 6281f86743
commit c58903846d
3 changed files with 118 additions and 246 deletions

View File

@@ -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' # 基础功能相关
],

View File

@@ -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)}")
def dummy_function(self):
"""占位函数"""
QMessageBox.information(self, "提示", "此功能暂未实现")

34
main.py
View File

@@ -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()