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'{step_title}:{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"

发现新版本: {update_info['version']}

" f"

更新内容:

" f"

{update_info['message']}

" ) 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"

{msg}

") 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"

检查更新失败: {str(e)}

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