1. 新增 CursorResetter 类,完整封装 cursor_win_id_modifier.ps1 的核心功能 2. 优化 AccountSwitcher 的重启逻辑,避免重复重启 3. 改进进程管理,移除 wmi 依赖,使用 tasklist 替代 4. 提升代码可维护性,后续只需更新 CursorResetter 即可适配脚本变更
2031 lines
74 KiB
Python
2031 lines
74 KiB
Python
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}") |