Files
nezhacursor/gui/main_window.py
huangzhenpc 8b2fbef54a feat(v3.4.7): 重构重置功能和优化重启逻辑
1. 新增 CursorResetter 类,完整封装 cursor_win_id_modifier.ps1 的核心功能

2. 优化 AccountSwitcher 的重启逻辑,避免重复重启

3. 改进进程管理,移除 wmi 依赖,使用 tasklist 替代

4. 提升代码可维护性,后续只需更新 CursorResetter 即可适配脚本变更
2025-02-14 15:06:05 +08:00

2031 lines
74 KiB
Python
Raw 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 sys
from pathlib import Path
import logging
import os
from PIL import Image
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QFrame, QTextEdit,
QMessageBox, QApplication, QSystemTrayIcon, QMenu,
QDialog, QProgressBar, QStyle)
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QIcon, QPixmap
import time
import requests
from urllib.parse import quote
import subprocess
sys.path.append(str(Path(__file__).parent.parent))
from utils.config import Config
from utils.version_manager import VersionManager
from account_switcher import AccountSwitcher
def get_version():
try:
version_file = Path(__file__).parent.parent / 'version.txt'
with open(version_file, 'r', encoding='utf-8') as f:
return f.read().strip()
except Exception as e:
logging.error(f"读取版本号失败: {str(e)}")
return "未知版本"
class LoadingDialog(QDialog):
"""加载对话框"""
def __init__(self, parent=None, message="请稍候..."):
super().__init__(parent)
self.setWindowTitle("处理中")
self.setFixedSize(300, 100)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
layout = QVBoxLayout()
# 添加消息标签
self.message_label = QLabel(message)
self.message_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.message_label)
# 添加进度条
self.progress_bar = QProgressBar()
self.progress_bar.setTextVisible(False)
self.progress_bar.setRange(0, 0) # 设置为循环模式
self.progress_bar.setMinimumWidth(250) # 设置最小宽度
layout.addWidget(self.progress_bar)
self.setLayout(layout)
# 设置样式
self.setStyleSheet("""
QDialog {
background-color: #f8f9fa;
}
QLabel {
color: #0d6efd;
font-size: 14px;
font-weight: bold;
padding: 10px;
}
QProgressBar {
border: 2px solid #e9ecef;
border-radius: 5px;
text-align: center;
min-height: 12px;
}
QProgressBar::chunk {
background-color: #0d6efd;
width: 15px;
margin: 0.5px;
}
""")
# 添加定时器以自动更新进度条动画
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_progress)
self.progress_value = 0
def update_progress(self):
"""更新进度条动画"""
self.progress_value = (self.progress_value + 1) % 100
self.progress_bar.setValue(self.progress_value)
def showEvent(self, event):
"""显示时启动定时器"""
super().showEvent(event)
self.timer.start(50) # 每50毫秒更新一次
def hideEvent(self, event):
"""隐藏时停止定时器"""
self.timer.stop()
super().hideEvent(event)
def check_status(self):
"""检查会员状态从API获取"""
try:
# 只在首次检查时显示加载对话框,并设置更明确的提示
if self._activation_status is None:
self.show_loading_dialog("正在连接服务器,请稍候...")
# 创建工作线程
self.worker = ApiWorker(self.switcher.get_member_status)
self.worker.finished.connect(self.on_status_check_complete)
self.worker.start()
except Exception as e:
if self._activation_status is None:
self.hide_loading_dialog()
QMessageBox.critical(self, "错误", f"检查状态失败: {str(e)}")
self._activation_status = False
logging.error(f"检查状态时发生错误: {str(e)}")
return False
class ApiWorker(QThread):
"""API请求工作线程"""
finished = pyqtSignal(tuple) # 发送结果信号
def __init__(self, func, *args, **kwargs):
super().__init__()
self.func = func
self.args = args
self.kwargs = kwargs
def run(self):
try:
result = self.func(*self.args, **self.kwargs)
self.finished.emit((True, result))
except Exception as e:
self.finished.emit((False, str(e)))
class UpdateWorker(QThread):
"""更新检查工作线程"""
progress = pyqtSignal(str) # 发送进度信息
finished = pyqtSignal(tuple) # 发送结果信号
def __init__(self, version_manager):
super().__init__()
self.version_manager = version_manager
def run(self):
try:
self.progress.emit("正在检查更新...")
has_update, is_force, version_info = self.version_manager.needs_update()
if has_update and version_info:
self.finished.emit((True, is_force, version_info))
else:
self.finished.emit((False, False, None))
except Exception as e:
self.finished.emit((False, False, str(e)))
class DownloadProgressDialog(QDialog):
"""下载进度对话框"""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("正在下载更新")
self.setFixedSize(400, 300)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
layout = QVBoxLayout()
# 添加图标
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_DesktopIcon).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
# 下载状态标签
self.status_label = QLabel("正在连接服务器...")
self.status_label.setAlignment(Qt.AlignCenter)
self.status_label.setStyleSheet("""
color: #0d6efd;
font-size: 14px;
font-weight: bold;
padding: 10px;
""")
layout.addWidget(self.status_label)
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setStyleSheet("""
QProgressBar {
border: 2px solid #e9ecef;
border-radius: 5px;
text-align: center;
min-height: 20px;
background-color: #f8f9fa;
}
QProgressBar::chunk {
background-color: #0d6efd;
border-radius: 3px;
}
""")
layout.addWidget(self.progress_bar)
# 下载信息框
info_frame = QFrame()
info_frame.setStyleSheet("""
QFrame {
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
margin: 10px;
padding: 20px;
}
QLabel {
color: #495057;
font-size: 14px;
padding: 8px;
margin: 2px;
background: white;
border-radius: 4px;
border: 1px solid #e9ecef;
}
""")
info_layout = QVBoxLayout(info_frame)
info_layout.setSpacing(10) # 增加标签之间的间距
# 下载速度
self.speed_label = QLabel("下载速度: --")
self.speed_label.setMinimumHeight(35) # 设置最小高度
info_layout.addWidget(self.speed_label)
# 文件大小
self.size_label = QLabel("文件大小: --")
self.size_label.setMinimumHeight(35) # 设置最小高度
info_layout.addWidget(self.size_label)
# 预计剩余时间
self.time_label = QLabel("预计剩余时间: --")
self.time_label.setMinimumHeight(35) # 设置最小高度
info_layout.addWidget(self.time_label)
layout.addWidget(info_frame)
self.setLayout(layout)
# 初始化变量
self.start_time = time.time()
self.last_update_time = time.time()
self.last_downloaded = 0
def update_progress(self, downloaded_size, total_size):
"""更新进度信息"""
if total_size > 0:
percentage = (downloaded_size / total_size) * 100
self.progress_bar.setValue(int(percentage))
# 计算下载速度
current_time = time.time()
time_diff = current_time - self.last_update_time
if time_diff >= 0.5: # 每0.5秒更新一次
speed = (downloaded_size - self.last_downloaded) / time_diff
self.last_downloaded = downloaded_size
self.last_update_time = current_time
# 更新下载信息
speed_text = self._format_speed(speed)
self.speed_label.setText(f"下载速度: {speed_text}")
# 更新文件大小信息
total_mb = total_size / (1024 * 1024)
downloaded_mb = downloaded_size / (1024 * 1024)
self.size_label.setText(f"文件大小: {downloaded_mb:.1f} MB / {total_mb:.1f} MB")
# 更新预计剩余时间
if speed > 0:
remaining_bytes = total_size - downloaded_size
remaining_time = remaining_bytes / speed
time_text = self._format_time(remaining_time)
self.time_label.setText(f"预计剩余时间: {time_text}")
# 更新状态文本
self.status_label.setText(f"正在下载更新... {percentage:.1f}%")
def _format_speed(self, speed_bytes):
"""格式化速度显示"""
if speed_bytes > 1024 * 1024:
return f"{speed_bytes / (1024 * 1024):.1f} MB/s"
elif speed_bytes > 1024:
return f"{speed_bytes / 1024:.1f} KB/s"
else:
return f"{speed_bytes:.1f} B/s"
def _format_time(self, seconds):
"""格式化时间显示"""
if seconds < 60:
return f"{seconds:.0f}"
elif seconds < 3600:
minutes = seconds / 60
return f"{minutes:.0f}分钟"
else:
hours = seconds / 3600
return f"{hours:.1f}小时"
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.config = Config()
self.switcher = AccountSwitcher()
self.version_manager = VersionManager()
# 添加激活状态缓存
self._activation_status = None # 缓存的激活状态
self._status_timer = None # 状态更新定时器
# 添加心跳定时器
self._heartbeat_timer = QTimer()
self._heartbeat_timer.timeout.connect(self.send_heartbeat)
self._heartbeat_timer.start(5 * 60 * 1000) # 每5分钟发送一次心跳
# 添加请求锁,防止重复提交
self._is_requesting = False
self._last_request_time = 0
self._request_cooldown = 2 # 请求冷却时间(秒)
version = get_version()
cursor_version = self.switcher.get_cursor_version()
self.setWindowTitle(f"听泉Cursor助手 v{version} (本机Cursor版本: {cursor_version})")
self.setMinimumSize(600, 680) # 增加最小宽度到600
# 设置窗口图标
icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "icon", "two.ico")
if os.path.exists(icon_path):
self.window_icon = QIcon(icon_path)
if not self.window_icon.isNull():
self.setWindowIcon(self.window_icon)
logging.info(f"成功设置窗口图标: {icon_path}")
else:
logging.warning("图标文件加载失败")
# 创建系统托盘图标
self.create_tray_icon()
# 创建主窗口部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 设置主窗口样式
central_widget.setStyleSheet("""
QWidget {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #f8f9fa,
stop:0.5 #ffffff,
stop:1 #f8f9fa);
}
QFrame {
border: none;
background: transparent;
}
""")
# 创建主布局
main_layout = QVBoxLayout(central_widget)
main_layout.setSpacing(12) # 减小区域间距
main_layout.setContentsMargins(20, 15, 20, 15) # 调整外边距
# 设备ID区域
device_frame = QFrame()
device_frame.setStyleSheet("""
QFrame {
background: transparent;
padding: 8px;
}
""")
device_layout = QHBoxLayout(device_frame)
device_layout.setContentsMargins(0, 0, 0, 0)
device_layout.addWidget(QLabel("设备识别码(勿动):"))
self.hardware_id_edit = QLineEdit(self.switcher.hardware_id)
self.hardware_id_edit.setReadOnly(True)
self.hardware_id_edit.setStyleSheet("""
QLineEdit {
background-color: #ffffff;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 5px;
color: #495057;
}
""")
device_layout.addWidget(self.hardware_id_edit)
copy_btn = QPushButton("复制ID")
copy_btn.setStyleSheet("""
QPushButton {
background-color: #6c757d;
color: white;
border: none;
padding: 5px 15px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #5a6268;
}
""")
copy_btn.clicked.connect(self.copy_device_id)
device_layout.addWidget(copy_btn)
main_layout.addWidget(device_frame)
# 会员状态区域
status_frame = QFrame()
status_frame.setStyleSheet("""
QFrame {
background: transparent;
padding: 8px;
}
QLabel {
color: #495057;
font-weight: bold;
}
QTextEdit {
background-color: #ffffff;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 5px;
color: #495057;
}
QTextEdit QScrollBar:vertical {
width: 8px;
background: transparent;
margin: 0px;
}
QTextEdit QScrollBar::handle:vertical {
background: #ced4da;
min-height: 30px;
border-radius: 4px;
}
QTextEdit QScrollBar::handle:vertical:hover {
background: #adb5bd;
}
QTextEdit QScrollBar::add-line:vertical,
QTextEdit QScrollBar::sub-line:vertical {
height: 0px;
}
QTextEdit QScrollBar::add-page:vertical,
QTextEdit QScrollBar::sub-page:vertical {
background: none;
}
""")
status_layout = QVBoxLayout(status_frame)
status_layout.setContentsMargins(0, 0, 0, 0)
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 = QFrame()
activation_frame.setStyleSheet("""
QFrame {
background: transparent;
padding: 12px 0;
}
""")
activation_layout = QVBoxLayout(activation_frame)
activation_layout.setContentsMargins(0, 0, 0, 0)
# 激活码输入区域
activation_layout.addWidget(QLabel("激活(叠加)会员,多个激活码可叠加整体时长"))
input_frame = QFrame()
input_layout = QHBoxLayout(input_frame)
input_layout.setSpacing(10)
input_layout.setContentsMargins(0, 0, 0, 0)
self.activation_edit = QLineEdit()
self.activation_edit.setPlaceholderText("请输入激活码")
self.activation_edit.setMinimumWidth(350) # 增加输入框宽度
self.activation_edit.setFixedHeight(35)
self.activation_edit.setStyleSheet("""
QLineEdit {
background-color: #ffffff;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 8px;
color: #495057;
font-size: 13px;
}
QLineEdit:focus {
border-color: #86b7fe;
}
""")
input_layout.addWidget(self.activation_edit)
# 激活按钮
activate_btn = QPushButton("激活")
activate_btn.setFixedWidth(100)
activate_btn.setFixedHeight(35)
activate_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
activate_btn.clicked.connect(self.activate_account)
input_layout.addWidget(activate_btn)
activation_layout.addWidget(input_frame)
# 使用说明
usage_label = QLabel()
usage_text = (
"<div style='padding: 15px 0;'>"
"<p style='margin-bottom: 10px; font-size: 15px;'><b style='color: #0d6efd;'>使用步骤说明:</b></p>"
"<p style='line-height: 2; font-size: 14px;'>"
"<span style='font-size: 14px; color: #dc3545;'><b>第一步:</b></span> "
"输入激活码点击<b style='color: #0d6efd;'>【激活】</b>按钮完成激活<br>"
"<span style='font-size: 14px; color: #198754;'><b>第二步:</b></span> "
"点击<b style='color: #0d6efd;'>【刷新Cursor编辑器授权】</b><span style='color: #198754;'>一般情况下刷新即可正常使用</span><br>"
"<span style='font-size: 14px; color: #fd7e14;'><b>如果无法对话:</b></span> "
"点击<b style='color: #198754;'>【突破Cursor0.45.x限制】</b>,然后重新刷新授权<br>"
"<span style='font-size: 14px; color: #6c757d;'><b>建议操作:</b></span> "
"点击<b style='color: #dc3545;'>【禁用Cursor版本更新】</b><span style='color: #6c757d;'>保持软件稳定运行</span>"
"</p>"
"</div>"
)
usage_label.setText(usage_text)
usage_label.setStyleSheet("""
QLabel {
color: #333333;
font-size: 13px;
background: transparent;
}
""")
usage_label.setTextFormat(Qt.RichText)
usage_label.setWordWrap(True)
activation_layout.addWidget(usage_label)
main_layout.addWidget(activation_frame)
# 操作按钮区域
btn_frame = QFrame()
btn_layout = QVBoxLayout(btn_frame)
btn_layout.setSpacing(15) # 增加按钮间距
btn_layout.setContentsMargins(0, 10, 0, 10)
# 按钮基础样式
button_style = """
QPushButton {
color: white;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
min-width: 500px;
}
"""
# 刷新授权按钮(主按钮)
refresh_btn = QPushButton("刷新 Cursor 编辑器授权")
refresh_btn.setStyleSheet(button_style + """
QPushButton {
background-color: #0d6efd;
padding: 15px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
refresh_btn.clicked.connect(self.refresh_cursor_auth)
refresh_btn.setMinimumHeight(60)
refresh_btn.setMinimumWidth(500)
btn_layout.addWidget(refresh_btn, 0, Qt.AlignCenter)
# 突破限制按钮
bypass_btn = QPushButton("突破 Cursor 0.45.x 限制")
bypass_btn.setStyleSheet(button_style + """
QPushButton {
background-color: #198754;
padding: 12px;
}
QPushButton:hover {
background-color: #157347;
}
""")
bypass_btn.clicked.connect(self.bypass_cursor_limit)
bypass_btn.setMinimumHeight(50)
bypass_btn.setMinimumWidth(500)
btn_layout.addWidget(bypass_btn, 0, Qt.AlignCenter)
# 禁用更新按钮
disable_update_btn = QPushButton("禁用 Cursor 版本更新")
disable_update_btn.setStyleSheet(button_style + """
QPushButton {
background-color: #dc3545;
padding: 12px;
}
QPushButton:hover {
background-color: #bb2d3b;
}
""")
disable_update_btn.clicked.connect(self.disable_cursor_update)
disable_update_btn.setMinimumHeight(50)
disable_update_btn.setMinimumWidth(500)
btn_layout.addWidget(disable_update_btn, 0, Qt.AlignCenter)
main_layout.addWidget(btn_frame)
# 检查更新按钮
update_frame = QFrame()
update_frame.setStyleSheet("""
QFrame {
background: transparent;
padding: 5px;
}
""")
update_layout = QHBoxLayout(update_frame)
update_layout.setContentsMargins(0, 0, 0, 0)
check_update_btn = QPushButton("检查更新")
check_update_btn.setStyleSheet("""
QPushButton {
background-color: #6c757d;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #5a6268;
}
""")
check_update_btn.clicked.connect(self.check_for_updates)
update_layout.addWidget(check_update_btn)
main_layout.addWidget(update_frame)
# 启动时检查一次状态
QTimer.singleShot(0, self.check_status)
# 启动时自动检查更新
QTimer.singleShot(1000, self.check_for_updates)
def create_tray_icon(self):
"""创建系统托盘图标"""
try:
# 确保QSystemTrayIcon可用
if not QSystemTrayIcon.isSystemTrayAvailable():
logging.error("系统托盘不可用")
return
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.window_icon)
self.tray_icon.setToolTip("听泉Cursor助手")
# 创建托盘菜单
self.tray_menu = QMenu() # 保存为实例变量
# 添加刷新重置按钮
refresh_action = self.tray_menu.addAction("刷新重置")
refresh_action.triggered.connect(self.refresh_cursor_auth)
show_action = self.tray_menu.addAction("显示主窗口")
show_action.triggered.connect(self.show_and_activate)
# 添加分隔线
self.tray_menu.addSeparator()
quit_action = self.tray_menu.addAction("退出")
quit_action.triggered.connect(self.quit_application)
# 设置托盘菜单
self.tray_icon.setContextMenu(self.tray_menu)
# 连接托盘图标的信号
self.tray_icon.activated.connect(self.on_tray_icon_activated)
# 显示托盘图标
self.tray_icon.show()
logging.info("系统托盘图标创建成功")
except Exception as e:
logging.error(f"创建系统托盘图标失败: {str(e)}")
def show_and_activate(self):
"""显示并激活窗口"""
self.show()
self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
self.activateWindow()
self.raise_() # 确保窗口在最前面
def quit_application(self):
"""退出应用程序"""
try:
# 停止定时器
if self._status_timer:
self._status_timer.stop()
# 移除托盘图标
if hasattr(self, 'tray_icon'):
self.tray_icon.setVisible(False) # 先隐藏托盘图标
self.tray_icon.deleteLater() # 删除托盘图标
# 退出应用程序
QApplication.instance().quit()
except Exception as e:
logging.error(f"退出应用程序时发生错误: {str(e)}")
QApplication.instance().quit()
def on_tray_icon_activated(self, reason):
"""处理托盘图标的点击事件"""
if reason in (QSystemTrayIcon.DoubleClick, QSystemTrayIcon.Trigger):
self.show_and_activate()
def closeEvent(self, event):
"""窗口关闭事件"""
try:
if hasattr(self, 'tray_icon') and self.tray_icon.isVisible():
event.ignore()
self.hide()
# 确保托盘图标显示
self.tray_icon.show()
self.tray_icon.showMessage(
"听泉Cursor助手",
"程序已最小化到系统托盘",
QSystemTrayIcon.Information,
2000
)
else:
# 如果托盘图标不可用,则正常退出
if self._status_timer:
self._status_timer.stop()
if hasattr(self, '_heartbeat_timer'):
self._heartbeat_timer.stop()
event.accept()
except Exception as e:
logging.error(f"处理关闭事件时发生错误: {str(e)}")
# 发生错误时,接受关闭事件
if self._status_timer:
self._status_timer.stop()
if hasattr(self, '_heartbeat_timer'):
self._heartbeat_timer.stop()
event.accept()
def copy_device_id(self):
"""复制设备ID到剪贴板"""
QApplication.clipboard().setText(self.hardware_id_edit.text())
QMessageBox.information(self, "提示", "设备ID已复制到剪贴板")
def show_loading_dialog(self, message="请稍候..."):
"""显示加载对话框"""
if not hasattr(self, 'loading_dialog') or not self.loading_dialog:
self.loading_dialog = LoadingDialog(self, message)
self.loading_dialog.setStyleSheet("""
QDialog {
background-color: #f8f9fa;
}
QLabel {
color: #0d6efd;
font-size: 14px;
font-weight: bold;
padding: 10px;
}
QProgressBar {
border: 2px solid #e9ecef;
border-radius: 5px;
text-align: center;
}
QProgressBar::chunk {
background-color: #0d6efd;
width: 10px;
margin: 0.5px;
}
""")
else:
self.loading_dialog.message_label.setText(message)
self.loading_dialog.show()
QApplication.processEvents() # 确保对话框立即显示
def hide_loading_dialog(self):
"""隐藏加载对话框"""
if hasattr(self, 'loading_dialog') and self.loading_dialog:
self.loading_dialog.hide()
self.loading_dialog.deleteLater()
self.loading_dialog = None
QApplication.processEvents() # 确保对话框立即隐藏
def activate_account(self):
"""激活账号"""
code = self.activation_edit.text().strip()
if not code:
# 创建自定义消息框
msg = QDialog(self)
msg.setWindowTitle("提示")
msg.setFixedWidth(400)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
# 创建布局
layout = QVBoxLayout()
# 添加图标和文本
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
text_label = QLabel("请输入激活码")
text_label.setAlignment(Qt.AlignCenter)
text_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #333333; padding: 10px;")
layout.addWidget(text_label)
# 添加购买信息
info_text = "获取会员激活码,请通过以下方式:\n\n" \
"• 官方自助网站cursor.nosqli.com\n" \
"• 微信客服behikcigar\n" \
"• 闲鱼店铺xxx\n\n" \
"————————————————————\n" \
"诚招代理商,欢迎加盟合作!"
info_label = QLabel(info_text)
info_label.setAlignment(Qt.AlignLeft)
info_label.setStyleSheet("""
QLabel {
color: #333333;
font-size: 14px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #dee2e6;
margin: 10px;
}
""")
layout.addWidget(info_label)
# 添加复制按钮区域
btn_layout = QHBoxLayout()
# 复制网站按钮
copy_web_btn = QPushButton("复制网站")
copy_web_btn.clicked.connect(lambda: self.copy_and_show_tip(msg, "cursor.nosqli.com", "网站地址已复制到剪贴板"))
copy_web_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
btn_layout.addWidget(copy_web_btn)
# 复制微信按钮
copy_wx_btn = QPushButton("复制微信")
copy_wx_btn.clicked.connect(lambda: self.copy_and_show_tip(msg, "behikcigar", "微信号已复制到剪贴板"))
copy_wx_btn.setStyleSheet("""
QPushButton {
background-color: #198754;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #157347;
}
""")
btn_layout.addWidget(copy_wx_btn)
# 确定按钮
ok_btn = QPushButton("确定")
ok_btn.clicked.connect(msg.accept)
ok_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 25px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
msg.setLayout(layout)
msg.exec_()
return
try:
# 显示加载对话框
self.show_loading_dialog("正在验证激活码,请稍候...")
# 创建工作线程
self.worker = ApiWorker(self.switcher.check_activation_code, code)
self.worker.finished.connect(self.on_activation_complete)
self.worker.start()
except Exception as e:
self.hide_loading_dialog()
msg = QMessageBox(self)
msg.setWindowTitle("错误")
msg.setText(f"激活失败: {str(e)}")
msg.setIcon(QMessageBox.Critical)
msg.setStyleSheet("""
QMessageBox {
background-color: #f8f9fa;
min-width: 450px;
padding: 20px;
}
QMessageBox QLabel {
color: #333333;
font-size: 14px;
padding: 10px;
margin: 10px;
}
QMessageBox QLabel:first-child {
font-weight: bold;
color: #dc3545;
}
QPushButton {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 25px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #bb2d3b;
}
""")
msg.exec_()
def copy_and_show_tip(self, parent, text, tip):
"""复制文本并显示提示"""
QApplication.clipboard().setText(text)
QMessageBox.information(parent, "提示", tip)
def on_activation_complete(self, result):
"""激活完成回调"""
success, data = result
self.hide_loading_dialog()
if isinstance(data, tuple):
success, message, account_info = data
if success and account_info is not None:
# 更新会员信息显示
self.update_status_display(account_info)
# 更新激活状态缓存
self._activation_status = account_info.get('status') == 'active'
# 更新状态定时器
if self._activation_status:
if self._status_timer:
self._status_timer.stop()
self._status_timer = QTimer(self)
self._status_timer.setSingleShot(False)
self._status_timer.timeout.connect(self.check_status)
self._status_timer.start(60 * 1000) # 每60秒检测一次
logging.info("已设置每分钟检测会员状态")
msg = QMessageBox(self)
msg.setWindowTitle("激活成功")
msg.setText("激活成功!")
msg.setInformativeText(message)
msg.setIcon(QMessageBox.Information)
msg.setStyleSheet("""
QMessageBox {
background-color: #f8f9fa;
min-width: 500px;
padding: 20px;
}
QMessageBox QLabel {
color: #333333;
font-size: 14px;
padding: 10px;
margin: 10px;
}
QMessageBox QLabel:first-child {
font-weight: bold;
font-size: 16px;
color: #198754;
}
QPushButton {
background-color: #198754;
color: white;
border: none;
padding: 8px 25px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
margin: 10px;
}
QPushButton:hover {
background-color: #157347;
}
""")
msg.exec_()
# 清空激活码输入框
self.activation_edit.clear()
else:
msg = QMessageBox(self)
msg.setWindowTitle("激活失败")
msg.setText(message)
msg.setIcon(QMessageBox.Critical)
msg.setStyleSheet("""
QMessageBox {
background-color: #f8f9fa;
min-width: 450px;
padding: 20px;
}
QMessageBox QLabel {
color: #333333;
font-size: 14px;
padding: 10px;
margin: 10px;
}
QMessageBox QLabel:first-child {
font-weight: bold;
color: #dc3545;
}
QPushButton {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 25px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
margin: 10px;
}
QPushButton:hover {
background-color: #bb2d3b;
}
""")
msg.exec_()
# 所有失败情况都不更新状态显示
else:
QMessageBox.critical(self, "错误", f"激活失败: {data}")
# 不更新状态显示
def update_status_display(self, status_info: dict):
"""更新状态显示"""
# 打印API返回的原始数据
logging.info("=== API返回数据 ===")
logging.info(f"状态信息: {status_info}")
# 更新状态文本
status_map = {
"active": "正常",
"inactive": "未激活",
"expired": "已过期"
}
status_text = status_map.get(status_info.get('status', 'inactive'), "未知")
# 构建状态文本
status_lines = [
f"会员状态:{status_text}",
f"到期时间:{status_info.get('expire_time', '')}",
f"总天数:{status_info.get('total_days', 0)}",
f"剩余天数:{status_info.get('days_left', 0)}"
]
# 添加设备信息
device_info = status_info.get('device_info', {})
if device_info:
status_lines.extend([
"",
"设备信息:",
f"系统:{device_info.get('os', '')}",
f"设备名:{device_info.get('device_name', '')}",
f"IP地址{device_info.get('ip', '')}",
f"地区:{device_info.get('location', '--')}"
])
# 更新状态文本
self.status_text.setPlainText("\n".join(status_lines))
def check_status(self):
"""检查会员状态从API获取"""
try:
# 只在首次检查时显示加载对话框
if self._activation_status is None:
self.show_loading_dialog("正在连接服务器...")
# 创建工作线程
self.worker = ApiWorker(self.switcher.get_member_status)
self.worker.finished.connect(self.on_status_check_complete)
self.worker.start()
except Exception as e:
if self._activation_status is None:
self.hide_loading_dialog()
QMessageBox.critical(self, "错误", f"检查状态失败: {str(e)}")
self._activation_status = False
logging.error(f"检查状态时发生错误: {str(e)}")
return False
def on_status_check_complete(self, result):
"""状态检查完成回调"""
success, data = result
# 只在首次检查时隐藏加载对话框
if self._activation_status is None:
self.hide_loading_dialog()
if success and data:
# 更新激活状态和定时器
self._activation_status = data.get('status') == 'active'
if self._activation_status:
# 设置定时器
if self._status_timer:
self._status_timer.stop()
self._status_timer = QTimer(self)
self._status_timer.setSingleShot(False)
self._status_timer.timeout.connect(self.check_status)
self._status_timer.start(60 * 1000) # 每60秒检测一次
logging.info("已设置每分钟检测会员状态")
else:
if self._status_timer:
self._status_timer.stop()
# 更新显示
self.update_status_display(data)
else:
# 停止定时器
if self._status_timer:
self._status_timer.stop()
self._activation_status = False
# 更新为未激活状态
device_info = self.switcher.get_device_info()
inactive_status = {
"status": "inactive",
"expire_time": "",
"total_days": 0,
"days_left": 0,
"device_info": device_info
}
self.update_status_display(inactive_status)
logging.warning("会员状态检查失败或未激活")
# 清除会员信息文件
try:
member_file = self.switcher.config.member_file
if member_file.exists():
member_file.unlink()
logging.info("已清除会员信息文件")
except Exception as e:
logging.error(f"清除会员信息文件时出错: {str(e)}")
return self._activation_status
def check_activation_status(self) -> bool:
"""检查是否已激活(使用缓存)
Returns:
bool: 是否已激活
"""
# 如果缓存的状态是None从API获取
if self._activation_status is None:
return self.check_status()
# 如果未激活,显示购买信息
if not self._activation_status:
self.show_purchase_info()
return self._activation_status
def show_purchase_info(self):
"""显示购买信息对话框"""
# 创建自定义消息框
msg = QDialog(self)
msg.setWindowTitle("会员未激活")
msg.setFixedWidth(400)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
# 创建布局
layout = QVBoxLayout()
# 添加图标和文本
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
text_label = QLabel("您还未激活会员或会员已过期")
text_label.setAlignment(Qt.AlignCenter)
text_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #333333; padding: 10px;")
layout.addWidget(text_label)
# 添加购买信息
info_text = "获取会员激活码,请通过以下方式:\n\n" \
"• 官方自助网站cursor.nosqli.com\n" \
"• 微信客服behikcigar\n" \
"• 闲鱼店铺xxx\n\n" \
"————————————————————\n" \
"诚招代理商,欢迎加盟合作!"
info_label = QLabel(info_text)
info_label.setAlignment(Qt.AlignLeft)
info_label.setStyleSheet("""
QLabel {
color: #333333;
font-size: 14px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #dee2e6;
margin: 10px;
}
""")
layout.addWidget(info_label)
# 添加复制按钮区域
btn_layout = QHBoxLayout()
# 复制网站按钮
copy_web_btn = QPushButton("复制网站")
copy_web_btn.clicked.connect(lambda: self.copy_and_show_tip(msg, "cursor.nosqli.com", "网站地址已复制到剪贴板"))
copy_web_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
btn_layout.addWidget(copy_web_btn)
# 复制微信按钮
copy_wx_btn = QPushButton("复制微信")
copy_wx_btn.clicked.connect(lambda: self.copy_and_show_tip(msg, "behikcigar", "微信号已复制到剪贴板"))
copy_wx_btn.setStyleSheet("""
QPushButton {
background-color: #198754;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #157347;
}
""")
btn_layout.addWidget(copy_wx_btn)
# 确定按钮
ok_btn = QPushButton("确定")
ok_btn.clicked.connect(msg.accept)
ok_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 25px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
msg.setLayout(layout)
msg.exec_()
def refresh_cursor_auth(self):
"""刷新Cursor授权"""
if not self.check_activation_status():
return
if not self._check_request_throttle():
return
try:
# 显示加载对话框
self.show_loading_dialog("正在刷新授权,请稍候...")
# 创建工作线程
self.worker = ApiWorker(self.switcher.refresh_cursor_auth)
self.worker.finished.connect(self.on_refresh_auth_complete)
self.worker.start()
except Exception as e:
self._request_complete()
self.hide_loading_dialog()
self.show_custom_error("刷新授权失败", str(e))
def on_refresh_auth_complete(self, result):
"""刷新授权完成回调"""
success, data = result
self.hide_loading_dialog()
self._request_complete()
if isinstance(data, tuple):
success, message = data
if success:
self.show_custom_message("成功", "刷新授权成功", message, QStyle.SP_DialogApplyButton, "#198754")
else:
self.show_custom_error("刷新授权失败", message)
else:
self.show_custom_error("刷新授权失败", str(data))
def disable_cursor_update(self):
"""禁用Cursor更新"""
if not self.check_activation_status():
return
if not self._check_request_throttle():
return
try:
# 显示加载对话框
self.show_loading_dialog("正在禁用更新,请稍候...")
# 创建工作线程
from utils.cursor_registry import CursorRegistry
registry = CursorRegistry()
def disable_func():
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 2. 处理updater文件
updater_path = Path(os.getenv('LOCALAPPDATA')) / "cursor-updater"
try:
# 如果是目录,则删除
if updater_path.is_dir():
import shutil
shutil.rmtree(str(updater_path))
logging.info("删除updater目录成功")
# 如果是文件,则删除
if updater_path.is_file():
updater_path.unlink()
logging.info("删除updater文件成功")
# 创建阻止文件
updater_path.touch()
logging.info("创建updater空文件成功")
# 设置文件权限
import subprocess
import stat
# 设置只读属性
os.chmod(str(updater_path), stat.S_IREAD)
logging.info("设置只读属性成功")
# 使用icacls设置权限只读
username = os.getenv('USERNAME')
cmd = f'icacls "{str(updater_path)}" /inheritance:r /grant:r "{username}:(R)"'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
logging.error(f"设置文件权限失败: {result.stderr}")
return False, "设置文件权限失败"
logging.info("设置文件权限成功")
# 验证设置
if not os.path.exists(updater_path):
return False, "文件创建失败"
if os.access(str(updater_path), os.W_OK):
return False, "文件权限设置失败"
except Exception as e:
logging.error(f"处理updater文件失败: {str(e)}")
return False, "处理updater文件失败"
# 3. 修改package.json配置
if not registry.fix_cursor_startup():
return False, "修改配置失败"
# 4. 重启Cursor
cursor_exe = registry.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
logging.info("Cursor重启成功")
return True, "Cursor更新已禁用程序已重启"
else:
return False, "未找到Cursor程序"
except Exception as e:
logging.error(f"禁用更新时发生错误: {str(e)}")
return False, str(e)
self.worker = ApiWorker(disable_func)
self.worker.finished.connect(lambda result: self.on_disable_update_complete(result))
self.worker.start()
except Exception as e:
self._request_complete()
self.hide_loading_dialog()
self.show_custom_error("禁用更新失败", str(e))
def on_disable_update_complete(self, result):
"""禁用更新完成回调"""
success, data = result
self.hide_loading_dialog()
self._request_complete()
if success:
self.show_custom_message(
"成功",
"禁用更新成功",
data,
QStyle.SP_DialogApplyButton,
"#198754"
)
else:
self.show_custom_error("禁用更新失败", str(data))
def bypass_cursor_limit(self):
"""突破Cursor版本限制"""
if not self.check_activation_status():
return
if not self._check_request_throttle():
return
try:
# 显示加载对话框
self.show_loading_dialog("正在突破版本限制,请稍候...")
# 创建工作线程
from utils.cursor_registry import CursorRegistry
registry = CursorRegistry()
def reset_func():
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
time.sleep(2)
# 2. 清理注册表
if not registry.clean_registry():
return False, "清理注册表失败"
# 3. 清理文件
if not registry.clean_cursor_files():
return False, "清理文件失败"
# 4. 重启Cursor
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
logging.info("Cursor重启成功")
return True, "突破限制成功"
else:
return False, "未找到Cursor程序"
except Exception as e:
logging.error(f"突破限制时发生错误: {str(e)}")
return False, str(e)
self.worker = ApiWorker(reset_func)
self.worker.finished.connect(self.on_bypass_complete)
self.worker.start()
except Exception as e:
self._request_complete()
self.hide_loading_dialog()
self.show_custom_error("突破限制失败", str(e))
def on_bypass_complete(self, result):
"""突破限制完成回调"""
success, data = result
self.hide_loading_dialog()
self._request_complete()
if success:
self.show_custom_message(
"成功",
"突破限制成功",
"Cursor版本限制已突破编辑器已重启。",
QStyle.SP_DialogApplyButton,
"#198754"
)
else:
self.show_custom_error("突破限制失败", str(data))
def show_custom_message(self, title, header, message, icon_type, color):
"""显示自定义消息框"""
msg = QDialog(self)
msg.setWindowTitle(title)
msg.setFixedWidth(400)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
layout = QVBoxLayout()
# 添加图标
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(icon_type).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
# 添加标题
text_label = QLabel(header)
text_label.setAlignment(Qt.AlignCenter)
text_label.setStyleSheet(f"""
font-size: 14px;
font-weight: bold;
color: {color};
padding: 10px;
""")
layout.addWidget(text_label)
# 添加详细信息
info_label = QLabel(message)
info_label.setAlignment(Qt.AlignLeft)
info_label.setWordWrap(True)
info_label.setStyleSheet("""
QLabel {
color: #333333;
font-size: 14px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #dee2e6;
margin: 10px;
}
""")
layout.addWidget(info_label)
# 确定按钮
btn_layout = QHBoxLayout()
ok_btn = QPushButton("确定")
ok_btn.clicked.connect(msg.accept)
ok_btn.setStyleSheet(f"""
QPushButton {{
background-color: {color};
color: white;
border: none;
padding: 8px 25px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}}
QPushButton:hover {{
background-color: {color.replace('fd', 'd7') if 'fd' in color else color.replace('54', '47')};
}}
""")
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
msg.setLayout(layout)
msg.exec_()
def show_custom_error(self, header, message):
"""显示自定义错误消息框"""
self.show_custom_message(
"错误",
header,
message,
QStyle.SP_MessageBoxCritical,
"#dc3545"
)
def _check_request_throttle(self) -> bool:
"""检查是否可以发送请求(防重复提交)
Returns:
bool: 是否可以发送请求
"""
current_time = time.time()
# 如果正在请求中返回False
if self._is_requesting:
return False
# 如果距离上次请求时间小于冷却时间返回False
if current_time - self._last_request_time < self._request_cooldown:
return False
# 更新状态和时间
self._is_requesting = True
self._last_request_time = current_time
return True
def _request_complete(self):
"""请求完成,重置状态"""
self._is_requesting = False
def check_for_updates(self):
"""检查更新"""
if self._is_requesting:
return
self._is_requesting = True
self.show_loading_dialog("正在检查更新...")
self.update_worker = UpdateWorker(self.version_manager)
self.update_worker.progress.connect(lambda msg: self.loading_dialog.message_label.setText(msg))
self.update_worker.finished.connect(self.on_update_check_complete)
self.update_worker.start()
def on_update_check_complete(self, result):
"""更新检查完成的回调"""
self.hide_loading_dialog()
self._is_requesting = False
has_update, is_force, version_info = result
if isinstance(version_info, str): # 发生错误
self.show_custom_error("检查更新失败", f"检查更新时发生错误: {version_info}")
return
if not has_update:
self.show_custom_message(
"检查更新",
"已是最新版本",
f"您当前使用的 v{self.version_manager.current_version} 已经是最新版本。",
QStyle.SP_DialogApplyButton,
"#198754"
)
return
# 显示更新信息
msg = QDialog(self)
msg.setWindowTitle("发现新版本")
msg.setFixedWidth(500)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
layout = QVBoxLayout()
# 添加图标
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxInformation).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
# 添加标题
title_label = QLabel("发现新版本")
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("""
font-size: 16px;
font-weight: bold;
color: #0d6efd;
padding: 10px;
""")
layout.addWidget(title_label)
# 版本信息
version_frame = QFrame()
version_frame.setStyleSheet("""
QFrame {
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
margin: 10px;
padding: 15px;
}
QLabel {
color: #333333;
font-size: 14px;
padding: 5px;
}
""")
version_layout = QVBoxLayout(version_frame)
# 当前版本
current_version_label = QLabel(f"当前版本v{self.version_manager.current_version}")
version_layout.addWidget(current_version_label)
# 最新版本
new_version = version_info.get('version_no', '未知').lstrip('v')
new_version_label = QLabel(f"最新版本v{new_version} ({version_info.get('version_name', '未知')})")
new_version_label.setStyleSheet("font-weight: bold; color: #0d6efd;")
version_layout.addWidget(new_version_label)
# 分割线
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setStyleSheet("background-color: #dee2e6;")
version_layout.addWidget(line)
# 更新说明
desc_label = QLabel("更新说明:")
desc_label.setStyleSheet("font-weight: bold; padding-top: 10px;")
version_layout.addWidget(desc_label)
desc_text = version_info.get('description', '')
desc_content = QLabel(desc_text if desc_text else "无更新说明")
desc_content.setWordWrap(True)
desc_content.setStyleSheet("padding: 5px 10px;")
version_layout.addWidget(desc_content)
# 强制更新提示
if is_force:
force_label = QLabel("* 此更新为强制更新,请立即更新!")
force_label.setStyleSheet("color: #dc3545; font-weight: bold; padding-top: 10px;")
version_layout.addWidget(force_label)
layout.addWidget(version_frame)
# 按钮区域
btn_layout = QHBoxLayout()
# 暂不更新按钮
if not is_force:
later_btn = QPushButton("暂不更新")
later_btn.setStyleSheet("""
QPushButton {
background-color: #6c757d;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #5a6268;
}
""")
later_btn.clicked.connect(msg.reject)
btn_layout.addWidget(later_btn)
# 立即更新按钮
update_btn = QPushButton("立即更新")
update_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
font-weight: bold;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
update_btn.clicked.connect(msg.accept)
btn_layout.addWidget(update_btn)
layout.addLayout(btn_layout)
msg.setLayout(layout)
# 显示对话框
if msg.exec_() == QDialog.Accepted:
self.download_update(version_info.get('download_url'))
def download_update(self, download_url):
"""下载更新"""
if not download_url:
self.show_custom_error("更新失败", "无法获取下载地址,请联系管理员")
return
try:
# 创建下载目录优先使用D盘如果不存在则使用当前程序目录
if Path("D:/").exists():
download_dir = Path("D:/CursorHelper/updates")
else:
download_dir = Path(__file__).parent.parent / "updates"
download_dir.mkdir(parents=True, exist_ok=True)
# 下载文件名
file_name = download_url.split('/')[-1]
save_path = download_dir / file_name
# 创建并显示进度对话框
progress_dialog = DownloadProgressDialog(self)
progress_dialog.show()
# 开始下载
try:
# 处理下载地址中的中文字符
url_parts = download_url.split('/')
url_parts[-1] = quote(url_parts[-1])
encoded_url = '/'.join(url_parts)
# 设置请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
response = requests.get(
encoded_url,
stream=True,
headers=headers,
timeout=30
)
response.raise_for_status()
# 获取文件大小
total_size = int(response.headers.get('content-length', 0))
if total_size == 0:
progress_dialog.hide()
self.show_custom_error("下载失败", "无法获取文件大小,下载地址可能无效")
return False, "无法获取文件大小"
# 更新进度条范围
progress_dialog.progress_bar.setRange(0, 100)
# 下载文件
downloaded_size = 0
block_size = 8192
with open(save_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=block_size):
if chunk:
f.write(chunk)
downloaded_size += len(chunk)
# 更新进度
progress_dialog.update_progress(downloaded_size, total_size)
QApplication.processEvents() # 保持界面响应
# 验证文件大小
actual_size = os.path.getsize(save_path)
if actual_size != total_size:
progress_dialog.hide()
os.remove(save_path)
self.show_custom_error("下载失败", "文件下载不完整,请重试")
return False, "文件下载不完整"
progress_dialog.hide()
# 显示下载完成对话框
self._show_download_complete_dialog(save_path)
return True, "下载成功"
except requests.exceptions.Timeout:
progress_dialog.hide()
self.show_custom_error("下载失败", "下载超时,请检查网络连接后重试")
return False, "下载超时"
except requests.exceptions.RequestException as e:
progress_dialog.hide()
self.show_custom_error("下载失败", f"下载出错: {str(e)}")
return False, str(e)
except Exception as e:
progress_dialog.hide()
self.show_custom_error("下载失败", f"下载过程中发生错误: {str(e)}")
return False, str(e)
except Exception as e:
self.show_custom_error("下载失败", f"下载更新时发生错误: {str(e)}")
return False, str(e)
def _show_download_complete_dialog(self, save_path):
"""显示下载完成对话框"""
# 创建自定义消息框
msg = QDialog(self)
msg.setWindowTitle("下载完成")
msg.setFixedWidth(500)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
layout = QVBoxLayout()
# 添加图标
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_DialogApplyButton).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
# 添加标题
title_label = QLabel("更新包下载成功")
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("""
font-size: 16px;
font-weight: bold;
color: #198754;
padding: 10px;
""")
layout.addWidget(title_label)
# 添加文件信息框
info_frame = QFrame()
info_frame.setStyleSheet("""
QFrame {
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
margin: 10px;
padding: 15px;
}
QLabel {
color: #333333;
font-size: 14px;
padding: 5px;
}
""")
info_layout = QVBoxLayout(info_frame)
# 文件路径
path_label = QLabel(f"文件保存在:\n{save_path}")
path_label.setWordWrap(True)
path_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
info_layout.addWidget(path_label)
# 文件大小
size_mb = save_path.stat().st_size / (1024 * 1024)
size_label = QLabel(f"文件大小:{size_mb:.2f} MB")
info_layout.addWidget(size_label)
layout.addWidget(info_frame)
# 提示信息
tip_label = QLabel("请关闭程序后运行更新包完成更新。")
tip_label.setStyleSheet("color: #dc3545; font-weight: bold; padding: 10px;")
layout.addWidget(tip_label)
# 按钮区域
btn_layout = QHBoxLayout()
# 打开文件按钮
open_file_btn = QPushButton("打开文件")
open_file_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
open_file_btn.clicked.connect(lambda: os.startfile(str(save_path)))
btn_layout.addWidget(open_file_btn)
# 打开文件夹按钮
open_dir_btn = QPushButton("打开文件夹")
open_dir_btn.setStyleSheet("""
QPushButton {
background-color: #198754;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #157347;
}
""")
open_dir_btn.clicked.connect(lambda: os.startfile(str(save_path.parent)))
btn_layout.addWidget(open_dir_btn)
# 退出按钮
quit_btn = QPushButton("立即退出")
quit_btn.setStyleSheet("""
QPushButton {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
font-weight: bold;
}
QPushButton:hover {
background-color: #bb2d3b;
}
""")
quit_btn.clicked.connect(lambda: (msg.accept(), self.quit_application()))
btn_layout.addWidget(quit_btn)
layout.addLayout(btn_layout)
msg.setLayout(layout)
msg.exec_()
def send_heartbeat(self):
"""发送心跳请求"""
if not self._check_request_throttle():
return
def heartbeat_func():
return self.switcher.send_heartbeat()
# 创建工作线程
self.heartbeat_worker = ApiWorker(heartbeat_func)
self.heartbeat_worker.finished.connect(self.on_heartbeat_complete)
self.heartbeat_worker.start()
def on_heartbeat_complete(self, result):
"""心跳完成回调"""
success, message = result
self._request_complete()
if success:
logging.info(f"心跳发送成功: {message}")
# 更新状态显示
self.check_status()
else:
logging.error(f"心跳发送失败: {message}")