Files
tingquanzhushou/gui/windows/main_window.py
huangzhenpc 2d603c33aa xxx
2025-05-17 18:16:24 +08:00

790 lines
29 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
from typing import Dict, Tuple, Any
from PyQt5.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QMessageBox, QLabel, QLineEdit, QPushButton,
QFrame, QTextEdit, QDesktopWidget, QSystemTrayIcon,
QMenu, QAction
)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont, QIcon, QDesktopServices
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from pathlib import Path
import os
from services.cursor_service import CursorService
from gui.components.widgets import (
LogWidget, StatusBar, ActionButton,
ActivationWidget, MemberStatusWidget,
ActivationStatusWidget, LoadingDialog
)
from gui.components.workers import ResetWorker, DisableWorker, RefreshTokenWorker
from common_utils import get_hardware_id, get_current_version
from utils.version_manager import VersionManager
class DeviceIdWidget(QFrame):
"""设备识别码显示组件"""
def __init__(self, device_id: str, parent=None):
super().__init__(parent)
self.setup_ui(device_id)
def setup_ui(self, device_id: str):
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0) # 移除边距
# 标签
label = QLabel("设备识别码(勿动):")
label.setStyleSheet("color: #dc3545; font-weight: bold;") # 红色警示
layout.addWidget(label)
# 显示设备ID的文本框
self.id_input = QLineEdit(device_id)
self.id_input.setReadOnly(True)
layout.addWidget(self.id_input)
# 复制按钮
copy_btn = QPushButton("复制ID")
copy_btn.clicked.connect(self.copy_device_id)
layout.addWidget(copy_btn)
self.setStyleSheet("""
QLineEdit {
background-color: #f8f9fa;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 5px;
color: #495057;
}
QPushButton {
background-color: #6c757d;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #5a6268;
}
""")
def copy_device_id(self):
"""复制设备ID到剪贴板"""
self.id_input.selectAll()
self.id_input.copy()
QMessageBox.information(self, "成功", "设备ID已复制到剪贴板")
class InstructionsWidget(QFrame):
"""使用步骤说明组件"""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 10) # 移除边距
# 标题
title = QLabel("使用步骤说明:")
title.setStyleSheet("color: #17a2b8; font-size: 14px; font-weight: bold;") # 青色标题
layout.addWidget(title)
# 步骤说明
steps = [
("初次使用", "输入激活码点击【激活】按钮完成激活(一个激活码只能用一次)"),
("日常使用说明", "点击【刷新Cursor编辑器授权】重启编辑器打开新的chat对话窗口 "),
("代理及对接号池", " 全球动态代理高品质号。不降智可使用3.7thinkingpro一样体验https://cursorpro.com.cn"),
# ("账号问题", "出现3.7拥挤或者vpn、ip等均不是账号问题 重启编辑器或者new新chat或者切换代理路线即可 切勿多次刷新账号\n"
# "现在账号紧缺后台防止盗号有限制供日常重度使用也是够的账号。\n"
# "如果账号问题可以联系我处理"),
# ("建议操作", "点击【禁用Cursor版本更新】个别机型因为权限无法禁用这个不影响使用")
]
for i, (step_title, step_content) in enumerate(steps):
step_label = QLabel(f"{step_title}{step_content}")
step_label.setWordWrap(True)
label_color = "#181818" if i == len(steps)-1 else "#495057"
step_label.setStyleSheet("""
QLabel {
color: {label_color};
margin: 3px 0;
min-height: 15px;
line-height: 15px;
}
""")
layout.addWidget(step_label)
# 给标题部分添加颜色
text = step_label.text()
# 最后一个标题标红,其他保持原来的青色
title_color = "#e0a800" if i == len(steps)-1 else "#17a2b8"
step_label.setText(f'<span style="color: {title_color};">{step_title}</span>{step_content}')
class MainWindow(QMainWindow):
"""主窗口"""
def __init__(self):
super().__init__()
self.cursor_service = CursorService()
self.version_manager = VersionManager()
self.current_worker = None
self.loading_dialog = None
# 初始化托盘图标
self.setup_tray()
self.setup_ui()
def setup_tray(self):
"""初始化托盘图标"""
# 创建托盘图标
self.tray_icon = QSystemTrayIcon(self)
# 设置图标
icon_path = Path(__file__).parent.parent.parent / "two.ico"
if icon_path.exists():
self.tray_icon.setIcon(QIcon(str(icon_path)))
# 创建托盘菜单
self.tray_menu = QMenu()
# 显示主窗口动作
show_action = QAction("显示主窗口", self)
show_action.triggered.connect(self.show_main_window)
self.tray_menu.addAction(show_action)
# 刷新授权动作
refresh_action = QAction("刷新 Cursor 编辑器授权", self)
refresh_action.triggered.connect(lambda: self.start_task('refresh'))
self.tray_menu.addAction(refresh_action)
# 退出动作
quit_action = QAction("退出", self)
quit_action.triggered.connect(self.quit_application)
self.tray_menu.addAction(quit_action)
# 设置托盘菜单
self.tray_icon.setContextMenu(self.tray_menu)
# 托盘图标双击事件
self.tray_icon.activated.connect(self.tray_icon_activated)
# 显示托盘图标
self.tray_icon.show()
def show_main_window(self):
"""显示主窗口"""
self.show()
self.activateWindow()
def quit_application(self):
"""退出应用程序"""
# 停止所有任务
if self.current_worker:
self.current_worker.stop()
# 移除托盘图标
self.tray_icon.setVisible(False)
# 退出应用
QApplication.quit()
def tray_icon_activated(self, reason):
"""托盘图标激活事件"""
if reason == QSystemTrayIcon.DoubleClick:
# 双击显示主窗口
self.show_main_window()
def showEvent(self, event):
"""窗口显示事件"""
super().showEvent(event)
# 在窗口显示后执行自检
QTimer.singleShot(500, self.auto_check) # 延迟500ms执行,确保界面完全显示
def show_loading(self, message: str):
"""显示加载对话框"""
if not self.loading_dialog:
self.loading_dialog = LoadingDialog(self)
self.loading_dialog.set_message(message)
self.loading_dialog.show()
QApplication.processEvents() # 确保对话框立即显示
def hide_loading(self):
"""隐藏加载对话框"""
if self.loading_dialog:
self.loading_dialog.hide()
def auto_check(self):
"""启动时自检"""
try:
# 1. 检查更新
has_update, msg, update_info = self.version_manager.check_update()
if has_update and update_info:
reply = QMessageBox.question(
self,
"发现新版本",
f"发现新版本: {update_info['version']}\n\n"
f"更新内容:\n{update_info['message']}\n\n"
"是否立即下载更新?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
QDesktopServices.openUrl(QUrl(update_info['download_url']))
# 2. 检查激活状态
self.update_member_status()
# 3. 恢复状态栏
self.status_bar.set_status("就绪")
except Exception as e:
self.status_bar.set_status("自检过程出现错误")
self.logger.error(f"自检失败: {str(e)}")
def setup_ui(self):
"""初始化UI"""
# 获取 Cursor 版本
cursor_version = "未知"
try:
package_paths = [
os.path.join(os.getenv('LOCALAPPDATA'), 'Programs', 'cursor', 'resources', 'app', 'package.json'),
os.path.join(os.getenv('LOCALAPPDATA'), 'cursor', 'resources', 'app', 'package.json')
]
for path in package_paths:
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
cursor_version = data.get('version', '未知')
break
except Exception as e:
print(f"获取 Cursor 版本失败: {e}")
self.setWindowTitle(f"听泉助手 v{get_current_version()} (本机Cursor版本{cursor_version})")
self.setMinimumSize(600, 500)
# 设置窗口图标
icon_path = Path(__file__).parent.parent.parent / "two.ico"
if icon_path.exists():
self.setWindowIcon(QIcon(str(icon_path)))
# 设置窗口背景为白色
self.setStyleSheet("""
QMainWindow {
background-color: white;
}
QWidget {
background-color: white;
}
""")
# 创建中心部件和布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
layout.setSpacing(15)
layout.setContentsMargins(15, 15, 15, 15)
# 设备识别码区域
device_id = get_hardware_id() # 使用common_utils中的函数
self.device_id_widget = DeviceIdWidget(device_id)
layout.addWidget(self.device_id_widget)
# 会员状态区域
self.member_status = MemberStatusWidget()
layout.addWidget(self.member_status)
# 激活区域
self.activation_widget = ActivationWidget(self) # 使用 self 作为父组件
layout.addWidget(self.activation_widget)
# 使用说明区域
self.instructions = InstructionsWidget()
layout.addWidget(self.instructions)
# 功能按钮区域
button_layout = QVBoxLayout()
button_layout.setSpacing(10)
# 刷新授权按钮 - 蓝色
self.refresh_button = QPushButton("刷新重置对话 Cursor 编辑器")
self.refresh_button.clicked.connect(lambda: self.start_task('refresh'))
self.refresh_button.setStyleSheet("""
QPushButton {
background-color: #007bff;
color: white;
border: none;
padding: 10px;
border-radius: 4px;
font-size: 15px;
min-height: 20px;
line-height: 20px;
}
QPushButton:hover {
background-color: #0056b3;
}
QPushButton:disabled {
background-color: #ccc;
}
""")
button_layout.addWidget(self.refresh_button)
# 实现限制按钮 - 绿色
self.limit_button = QPushButton("实现 Cursor 0.45.x 限制")
self.limit_button.clicked.connect(lambda: self.start_task('limit'))
self.limit_button.setStyleSheet("""
QPushButton {
background-color: #28a745;
color: white;
border: none;
padding: 12px;
border-radius: 4px;
font-size: 14px;
}
QPushButton:hover {
background-color: #218838;
}
QPushButton:disabled {
background-color: #ccc;
}
""")
# button_layout.addWidget(self.limit_button)
# 禁用更新按钮 - 红色
self.disable_button = QPushButton("禁用 Cursor 版本更新")
self.disable_button.clicked.connect(lambda: self.start_task('disable'))
self.disable_button.setStyleSheet("""
QPushButton {
background-color: #dc3545;
color: white;
border: none;
padding: 10px;
border-radius: 4px;
font-size: 14px;
min-height: 20px;
line-height: 20px;
}
QPushButton:hover {
background-color: #c82333;
}
QPushButton:disabled {
background-color: #ccc;
}
""")
button_layout.addWidget(self.disable_button)
layout.addLayout(button_layout)
# 购买链接按钮
self.purchase_button = QPushButton("购买(代理产品)激活码")
self.purchase_button.clicked.connect(self.open_purchase_link)
self.purchase_button.setStyleSheet("""
QPushButton {
background-color: #ffc107;
color: white;
border: none;
padding: 10px;
border-radius: 4px;
font-size: 14px;
margin-bottom: 10px;
min-height: 20px;
line-height: 20px;
}
QPushButton:hover {
background-color: #e0a800;
}
QPushButton:disabled {
background-color: #ccc;
}
""")
button_layout.addWidget(self.purchase_button)
# 检查更新按钮 - 灰色
self.check_update_button = QPushButton("检查更新")
self.check_update_button.clicked.connect(self.check_update)
self.check_update_button.setStyleSheet("""
QPushButton {
background-color: #6c757d;
color: white;
border: none;
padding: 8px;
border-radius: 4px;
margin-top: 5px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #5a6268;
}
QPushButton:disabled {
background-color: #cccccc;
}
""")
layout.addWidget(self.check_update_button)
# 状态栏
self.status_bar = StatusBar()
layout.addWidget(self.status_bar)
# 初始化状态
self.update_member_status()
# 检查是否有未完成的更新
self.check_pending_update()
def update_member_status(self):
"""更新会员状态"""
status = self.cursor_service.check_activation_status()
self.member_status.update_status(status)
# 根据激活状态更新按钮可用性
buttons_enabled = status["is_activated"]
self.refresh_button.setEnabled(buttons_enabled)
self.limit_button.setEnabled(buttons_enabled)
self.disable_button.setEnabled(buttons_enabled)
def handle_activation(self, code: str):
"""处理激活码激活"""
try:
print(f"MainWindow 收到激活请求,激活码:{code}")
# 更新状态栏
print("更新状态栏:正在激活...")
self.status_bar.set_status("正在激活...")
# 禁用激活按钮
print("禁用激活按钮")
self.activation_widget.activate_btn.setEnabled(False)
# 调用激活服务
print("调用激活服务...")
success, msg = self.cursor_service.activate_with_code(code)
print(f"激活结果success={success}, msg={msg}")
if success:
QMessageBox.information(self, "成功", msg)
self.update_member_status()
else:
QMessageBox.warning(self, "失败", msg)
# 清空输入框
self.activation_widget.clear_input()
except Exception as e:
print(f"激活过程发生错误:{str(e)}")
QMessageBox.critical(self, "错误", f"激活过程发生错误:{str(e)}")
finally:
# 恢复状态栏
print("恢复状态栏和按钮状态")
self.status_bar.set_status("就绪")
# 恢复激活按钮
self.activation_widget.activate_btn.setEnabled(True)
def start_task(self, task_type: str):
"""启动任务"""
# 检查激活状态
if not self.cursor_service.check_activation_status()["is_activated"]:
self.activation_widget.show_activation_required()
return
# 停止当前任务(如果有)
if self.current_worker:
self.current_worker.stop()
# 禁用所有按钮
self.refresh_button.setEnabled(False)
self.limit_button.setEnabled(False)
self.disable_button.setEnabled(False)
# 创建并启动工作线程
if task_type == 'refresh':
self.status_bar.set_status("正在刷新授权...")
worker = RefreshTokenWorker()
worker.progress.connect(self.update_progress)
worker.finished.connect(self.handle_result)
worker.error.connect(self.handle_error)
worker.start()
self.current_worker = worker
elif task_type == 'limit':
self.status_bar.set_status("正在设置版本限制...")
worker = ResetWorker()
worker.progress.connect(self.update_progress)
worker.finished.connect(self.handle_result)
worker.error.connect(self.handle_error)
worker.start()
self.current_worker = worker
elif task_type == 'disable':
self.status_bar.set_status("正在禁用更新...")
worker = DisableWorker()
worker.progress.connect(self.update_progress)
worker.finished.connect(self.handle_result)
worker.error.connect(self.handle_error)
worker.start()
self.current_worker = worker
def update_progress(self, info: dict):
"""更新进度信息"""
self.status_bar.set_status(info.get('message', '处理中...'))
def show_solution_dialog(self) -> None:
"""显示通用解决方案对话框"""
solution_msg = (
"请按照以下步骤操作:\n\n"
"1. 以管理员身份运行 PowerShell\n"
"2. 复制并运行以下命令:\n\n"
"irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/maticarmy/cursor-nosqli-tools/refs/heads/main/scripts/run/cursor_win_id_modifier.ps1 | iex\n\n"
"是否复制此命令到剪贴板?"
)
reply = QMessageBox.question(
self,
"解决方案",
solution_msg,
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
clipboard = QApplication.clipboard()
clipboard.setText("irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/maticarmy/cursor-nosqli-tools/refs/heads/main/scripts/run/cursor_win_id_modifier.ps1 | iex")
QMessageBox.information(self, "成功", "命令已复制到剪贴板")
def show_error_with_solution(self, title: str, message: str) -> None:
"""显示带有解决方案按钮的错误对话框
:param title: 对话框标题
:param message: 错误信息
"""
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Warning)
msg_box.setWindowTitle(title)
msg_box.setText(message)
# 添加解决方案按钮
solution_button = msg_box.addButton("解决方案", QMessageBox.ActionRole)
msg_box.addButton("关闭", QMessageBox.RejectRole)
msg_box.exec_()
# 如果点击了解决方案按钮
if msg_box.clickedButton() == solution_button:
self.show_solution_dialog()
def handle_result(self, result: Tuple[str, Any]):
"""处理工作线程的结果"""
try:
self.hide_loading()
action, data = result
if action == 'reset':
success, msg, _ = data
if success:
self.status_bar.set_status("重置成功")
QMessageBox.information(self, "成功", msg)
else:
self.status_bar.set_status("重置失败")
self.show_error_with_solution("操作失败", msg)
elif action == 'disable':
success, msg = data
if success:
self.status_bar.set_status("禁用更新成功")
QMessageBox.information(self, "成功", msg)
else:
self.status_bar.set_status("禁用更新失败")
self.show_error_with_solution("操作失败", msg)
elif action == 'refresh':
success, msg = data
if success:
self.status_bar.set_status("刷新成功")
QMessageBox.information(self, "成功", msg)
else:
self.status_bar.set_status("刷新失败")
self.show_error_with_solution("操作失败", msg)
except Exception as e:
self.logger.error(f"处理结果时出错: {str(e)}")
self.status_bar.set_status("处理结果时出错")
finally:
# 恢复按钮状态
self.update_member_status() # 确保按钮状态恢复
def handle_error(self, error_msg: str):
"""处理错误"""
self.status_bar.set_status("发生错误")
QMessageBox.critical(self, "错误", f"操作失败:{error_msg}")
# 重新启用按钮
self.update_member_status() # 确保按钮状态恢复
# 清理工作线程
if self.current_worker:
self.current_worker.stop()
def check_update(self):
"""手动检查更新"""
try:
# 禁用更新按钮
self.check_update_button.setEnabled(False)
self.status_bar.set_status("正在检查更新...")
# 检查更新
has_update, msg, update_info = self.version_manager.check_update()
if has_update and update_info:
# 创建自定义消息框
msg_box = QMessageBox(self)
msg_box.setWindowTitle("发现新版本")
msg_box.setIcon(QMessageBox.Information)
# 设置文本
text = (
f"<p style='font-size: 14px;'><b>发现新版本: {update_info['version']}</b></p>"
f"<p style='font-size: 13px; margin: 10px 0;'><b>更新内容:</b></p>"
f"<p style='font-size: 13px; white-space: pre-wrap;'>{update_info['message']}</p>"
)
msg_box.setText(text)
# 设置按钮
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
yes_btn = msg_box.button(QMessageBox.Yes)
no_btn = msg_box.button(QMessageBox.No)
yes_btn.setText("立即下载")
no_btn.setText("暂不更新")
# 设置样式
msg_box.setStyleSheet("""
QMessageBox {
background-color: white;
}
QPushButton {
min-width: 85px;
min-height: 24px;
padding: 4px 15px;
border-radius: 3px;
background-color: #007bff;
color: white;
border: none;
}
QPushButton:hover {
background-color: #0056b3;
}
QPushButton[text='暂不更新'] {
background-color: #6c757d;
}
QPushButton[text='暂不更新']:hover {
background-color: #5a6268;
}
""")
# 设置最小宽度
msg_box.setMinimumWidth(400)
# 显示对话框
if msg_box.exec_() == QMessageBox.Yes:
QDesktopServices.openUrl(QUrl(update_info['download_url']))
else:
# 创建自定义消息框
msg_box = QMessageBox(self)
msg_box.setWindowTitle("检查更新")
msg_box.setIcon(QMessageBox.Information)
msg_box.setText(f"<p style='font-size: 13px;'>{msg}</p>")
msg_box.setStyleSheet("""
QMessageBox {
background-color: white;
}
QPushButton {
min-width: 85px;
min-height: 24px;
padding: 4px 15px;
border-radius: 3px;
background-color: #007bff;
color: white;
border: none;
}
QPushButton:hover {
background-color: #0056b3;
}
""")
msg_box.setMinimumWidth(300)
msg_box.exec_()
except Exception as e:
error_box = QMessageBox(self)
error_box.setWindowTitle("错误")
error_box.setIcon(QMessageBox.Warning)
error_box.setText(f"<p style='font-size: 13px;'>检查更新失败: {str(e)}</p>")
error_box.setStyleSheet("""
QMessageBox {
background-color: white;
}
QPushButton {
min-width: 85px;
min-height: 24px;
padding: 4px 15px;
border-radius: 3px;
background-color: #dc3545;
color: white;
border: none;
}
QPushButton:hover {
background-color: #c82333;
}
""")
error_box.setMinimumWidth(300)
error_box.exec_()
finally:
# 恢复按钮状态和状态栏
self.check_update_button.setEnabled(True)
self.status_bar.set_status("就绪")
def check_pending_update(self):
"""检查是否有未完成的更新"""
try:
update_info = self.version_manager.get_last_update_info()
if update_info:
reply = QMessageBox.question(
self,
"未完成的更新",
f"检测到未完成的更新: {update_info['version']}\n\n"
f"更新内容:\n{update_info['message']}\n\n"
"是否现在更新?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
# 打开下载链接
QDesktopServices.openUrl(QUrl(update_info['download_url']))
else:
# 清除更新信息
self.version_manager.clear_update_info()
except Exception as e:
self.logger.error(f"检查未完成更新失败: {str(e)}")
def closeEvent(self, event):
"""窗口关闭事件"""
if self.tray_icon.isVisible():
# 最小化到托盘
QMessageBox.information(
self,
"提示",
"程序将继续在后台运行,双击托盘图标可以重新打开主窗口。"
)
self.hide()
event.ignore()
else:
# 停止所有正在运行的任务
if self.current_worker:
self.current_worker.stop()
event.accept()
def open_purchase_link(self):
"""打开购买链接"""
self.activation_widget.show_activation_required()