diff --git a/account_switcher.py b/account_switcher.py index aceec33..5909c35 100644 --- a/account_switcher.py +++ b/account_switcher.py @@ -13,7 +13,12 @@ from pathlib import Path from utils.config import Config from utils.cursor_registry import CursorRegistry from cursor_auth_manager import CursorAuthManager -from utils.cursor_resetter import CursorResetter # 添加导入 +from utils.cursor_resetter import CursorResetter +from datetime import datetime + +# 添加缓存文件路径常量 +CACHE_DIR = Path(os.path.expanduser("~")) / ".cursor_cache" +HARDWARE_ID_CACHE = CACHE_DIR / "hardware_id.json" def is_admin() -> bool: """检查是否具有管理员权限 @@ -56,7 +61,11 @@ def run_as_admin(): return False def get_hardware_id() -> str: - """获取硬件唯一标识""" + """获取硬件唯一标识 + 方案1: CPU ID + 主板序列号 + BIOS序列号 + 方案2: 系统盘序列号 + Windows安装时间 + 方案3: 计算机名(最后的备选方案) + """ try: # 创建startupinfo对象来隐藏命令行窗口 startupinfo = None @@ -64,26 +73,70 @@ def get_hardware_id() -> str: startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE - - # 获取CPU信息 - cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode() - cpu_id = cpu_info.split('\n')[1].strip() - - # 获取主板序列号 - board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode() - board_id = board_info.split('\n')[1].strip() - - # 获取BIOS序列号 - bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode() - bios_id = bios_info.split('\n')[1].strip() - - # 组合信息并生成哈希 - combined = f"{cpu_id}:{board_id}:{bios_id}" - return hashlib.md5(combined.encode()).hexdigest() + + # 方案1: 尝试获取硬件信息 + try: + # 获取CPU ID + cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode() + cpu_id = cpu_info.split('\n')[1].strip() + + # 获取主板序列号 + board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode() + board_id = board_info.split('\n')[1].strip() + + # 获取BIOS序列号 + bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode() + bios_id = bios_info.split('\n')[1].strip() + + # 如果所有信息都获取成功且有效 + if all([cpu_id, board_id, bios_id]) and not all(x in ['', '0', 'None', 'To be filled by O.E.M.'] for x in [cpu_id, board_id, bios_id]): + combined = f"{cpu_id}:{board_id}:{bios_id}" + hardware_id = hashlib.md5(combined.encode()).hexdigest() + logging.info("使用硬件信息生成ID成功") + return hardware_id + + except Exception as e: + logging.warning(f"方案1失败: {str(e)}") + + # 方案2: 系统盘序列号 + Windows安装时间 + try: + backup_info = [] + + # 获取系统盘序列号 + volume_info = subprocess.check_output('wmic logicaldisk where "DeviceID=\'C:\'" get VolumeSerialNumber', startupinfo=startupinfo).decode() + volume_serial = volume_info.split('\n')[1].strip() + if volume_serial and volume_serial not in ['', '0']: + backup_info.append(("volume", volume_serial)) + + # 获取Windows安装时间 + os_info = subprocess.check_output('wmic os get InstallDate', startupinfo=startupinfo).decode() + install_date = os_info.split('\n')[1].strip() + if install_date: + backup_info.append(("install", install_date)) + + if backup_info: + combined = "|".join(f"{k}:{v}" for k, v in sorted(backup_info)) + hardware_id = hashlib.md5(combined.encode()).hexdigest() + logging.info("使用系统信息生成ID成功") + return hardware_id + + except Exception as e: + logging.warning(f"方案2失败: {str(e)}") + + # 方案3: 使用计算机名(最后的备选方案) + import platform + computer_name = platform.node() + if computer_name: + hardware_id = hashlib.md5(computer_name.encode()).hexdigest() + logging.info("使用计算机名生成ID成功") + return hardware_id + + raise ValueError("无法获取任何可用信息来生成硬件ID") + except Exception as e: - logging.error(f"获取硬件ID失败: {str(e)}") - # 如果获取失败,使用UUID作为备选方案 - return str(uuid.uuid4()) + error_msg = f"生成硬件ID失败: {str(e)}" + logging.error(error_msg) + raise RuntimeError(error_msg) class AccountSwitcher: def __init__(self): @@ -101,9 +154,9 @@ class AccountSwitcher: self.package_json = self.app_path / "package.json" self.auth_manager = CursorAuthManager() self.config = Config() - self.hardware_id = self.get_hardware_id() # 先获取硬件ID - self.registry = CursorRegistry() # 添加注册表操作工具类 - self.resetter = CursorResetter() # 添加重置工具类 + self.hardware_id = get_hardware_id() # 使用新的硬件ID获取函数 + self.registry = CursorRegistry() + self.resetter = CursorResetter() self.max_retries = 5 self.wait_time = 1 @@ -111,33 +164,7 @@ class AccountSwitcher: def get_hardware_id(self) -> str: """获取硬件唯一标识""" - try: - # 创建startupinfo对象来隐藏命令行窗口 - startupinfo = None - if sys.platform == "win32": - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = subprocess.SW_HIDE - - # 获取CPU信息 - cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode() - cpu_id = cpu_info.split('\n')[1].strip() - - # 获取主板序列号 - board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode() - board_id = board_info.split('\n')[1].strip() - - # 获取BIOS序列号 - bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode() - bios_id = bios_info.split('\n')[1].strip() - - # 组合信息并生成哈希 - combined = f"{cpu_id}:{board_id}:{bios_id}" - return hashlib.md5(combined.encode()).hexdigest() - except Exception as e: - logging.error(f"获取硬件ID失败: {str(e)}") - # 如果获取失败,使用UUID作为备选方案 - return str(uuid.uuid4()) + return get_hardware_id() # 使用全局函数 def get_cursor_version(self) -> str: """获取Cursor版本号""" @@ -387,24 +414,40 @@ class AccountSwitcher: shell=True ) - # 等待进程关闭 + # 等待进程关闭,增加重试次数和等待时间 retry_count = 0 - while retry_count < self.max_retries: - if not self.get_process_details("Cursor.exe"): + max_retries = 10 # 增加最大重试次数 + wait_time = 2 # 增加每次等待时间 + + while retry_count < max_retries: + remaining_processes = self.get_process_details("Cursor.exe") + if not remaining_processes: logging.info("所有Cursor进程已关闭") + # 额外等待一段时间确保系统资源完全释放 + time.sleep(2) return True retry_count += 1 - if retry_count >= self.max_retries: + if retry_count >= max_retries: processes = self.get_process_details("Cursor.exe") if processes: logging.error(f"无法关闭以下进程:") for p in processes: logging.error(f"PID={p['pid']}, 路径={p['name']}") + # 最后一次尝试强制结束 + try: + subprocess.run( + "taskkill /f /im Cursor.exe /t >nul 2>&1", + startupinfo=startupinfo, + shell=True + ) + time.sleep(2) + except: + pass return False - logging.warning(f"等待进程关闭, 尝试 {retry_count}/{self.max_retries}...") - time.sleep(self.wait_time) + logging.warning(f"等待进程关闭, 尝试 {retry_count}/{max_retries}...") + time.sleep(wait_time) return True else: @@ -456,61 +499,74 @@ class AccountSwitcher: logging.error("无法关闭Cursor进程") return False - # 等待进程完全关闭 - time.sleep(2) + # 等待系统资源释放 + time.sleep(3) # 启动Cursor if sys.platform == "win32": cursor_exe = self.cursor_path / "Cursor.exe" if cursor_exe.exists(): - try: - # 使用subprocess启动 - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - - subprocess.Popen( - str(cursor_exe), - startupinfo=startupinfo, - creationflags=subprocess.CREATE_NEW_CONSOLE - ) - - # 等待进程启动 - time.sleep(3) - - # 验证进程是否启动 - processes = self.get_process_details("Cursor.exe") - if processes: - logging.info("Cursor启动成功") - return True - else: - logging.error("Cursor进程未找到") - # 尝试使用 os.startfile 作为备选方案 + max_retries = 3 + for attempt in range(max_retries): + try: + # 使用subprocess启动 + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + subprocess.Popen( + str(cursor_exe), + startupinfo=startupinfo, + creationflags=subprocess.CREATE_NEW_CONSOLE + ) + + # 等待进程启动 + time.sleep(5) # 增加等待时间 + + # 验证进程是否启动 + processes = self.get_process_details("Cursor.exe") + if processes: + logging.info("Cursor启动成功") + return True + else: + if attempt < max_retries - 1: + logging.warning(f"启动尝试 {attempt + 1} 失败,准备重试...") + time.sleep(3) + continue + + logging.error("Cursor进程未找到") + # 尝试使用 os.startfile 作为备选方案 + try: + os.startfile(str(cursor_exe)) + time.sleep(5) + logging.info("使用备选方案启动Cursor") + return True + except Exception as e: + logging.error(f"备选启动方案失败: {str(e)}") + return False + + except Exception as e: + if attempt < max_retries - 1: + logging.warning(f"启动尝试 {attempt + 1} 失败: {str(e)},准备重试...") + time.sleep(3) + continue + + logging.error(f"启动Cursor失败: {str(e)}") + # 尝试使用 os.startfile 作为最后的备选方案 try: os.startfile(str(cursor_exe)) - time.sleep(3) + time.sleep(5) logging.info("使用备选方案启动Cursor") return True except Exception as e: logging.error(f"备选启动方案失败: {str(e)}") return False - - except Exception as e: - logging.error(f"启动Cursor失败: {str(e)}") - # 尝试使用 os.startfile 作为备选方案 - try: - os.startfile(str(cursor_exe)) - time.sleep(3) - logging.info("使用备选方案启动Cursor") - return True - except Exception as e: - logging.error(f"备选启动方案失败: {str(e)}") - return False else: logging.error(f"未找到Cursor程序: {cursor_exe}") return False elif sys.platform == "darwin": try: subprocess.run("open -a Cursor", shell=True, check=True) + time.sleep(5) logging.info("Cursor启动成功") return True except subprocess.CalledProcessError as e: @@ -519,6 +575,7 @@ class AccountSwitcher: elif sys.platform == "linux": try: subprocess.run("cursor &", shell=True, check=True) + time.sleep(5) logging.info("Cursor启动成功") return True except subprocess.CalledProcessError as e: @@ -529,16 +586,6 @@ class AccountSwitcher: except Exception as e: logging.error(f"重启Cursor失败: {str(e)}") - # 尝试使用 os.startfile 作为最后的备选方案 - try: - cursor_exe = self.cursor_path / "Cursor.exe" - if cursor_exe.exists(): - os.startfile(str(cursor_exe)) - time.sleep(3) - logging.info("使用最终备选方案启动Cursor") - return True - except: - pass return False def activate_and_switch(self, activation_code: str) -> Tuple[bool, str]: diff --git a/gui/main_window_new.py b/gui/main_window_new.py deleted file mode 100644 index e8bba7c..0000000 --- a/gui/main_window_new.py +++ /dev/null @@ -1,145 +0,0 @@ -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 - -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) # 调整外边距 - - # 启动时检查一次状态 - QTimer.singleShot(0, self.check_status) - - # 启动时自动检查更新 - QTimer.singleShot(1000, self.check_for_updates) - - 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}") - - 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() \ No newline at end of file diff --git a/test_computer_name.py b/test_computer_name.py new file mode 100644 index 0000000..6f00eb4 --- /dev/null +++ b/test_computer_name.py @@ -0,0 +1,54 @@ +import platform +import hashlib +import time + +def get_computer_name_id(): + """使用计算机名生成ID(方案三)""" + try: + computer_name = platform.node() + if computer_name: + print(f"\n计算机名: {computer_name}") + hardware_id = hashlib.md5(computer_name.encode()).hexdigest() + return hardware_id + return None + except Exception as e: + print(f"获取计算机名失败: {str(e)}") + return None + +def test_stability(): + """测试ID的稳定性""" + print("开始测试计算机名生成ID的稳定性...") + print("将进行10次测试,每次间隔1秒") + + # 存储每次生成的ID + computer_ids = [] + + for i in range(10): + print(f"\n第 {i+1} 次测试:") + + # 测试计算机名方案 + comp_id = get_computer_name_id() + if comp_id: + print(f"使用计算机名生成的ID: {comp_id}") + computer_ids.append(comp_id) + + time.sleep(1) + + # 分析结果 + print("\n测试结果分析:") + + if computer_ids: + unique_ids = set(computer_ids) + print(f"\n计算机名方案:") + print(f"生成次数: {len(computer_ids)}") + print(f"唯一ID数: {len(unique_ids)}") + print("是否稳定: " + ("是" if len(unique_ids) == 1 else "否")) + if len(unique_ids) > 1: + print("出现的不同ID:") + for idx, id in enumerate(unique_ids): + print(f"{idx+1}. {id}") + else: + print("\n计算机名方案: 获取失败") + +if __name__ == "__main__": + test_stability() \ No newline at end of file diff --git a/test_hardware_id.py b/test_hardware_id.py new file mode 100644 index 0000000..844b716 --- /dev/null +++ b/test_hardware_id.py @@ -0,0 +1,131 @@ +import subprocess +import hashlib +import logging +import sys +import time + +def get_hardware_info(): + """获取硬件信息(方案一)""" + startupinfo = None + if sys.platform == "win32": + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE + + try: + # 获取CPU ID + cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode() + cpu_id = cpu_info.split('\n')[1].strip() + + # 获取主板序列号 + board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode() + board_id = board_info.split('\n')[1].strip() + + # 获取BIOS序列号 + bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode() + bios_id = bios_info.split('\n')[1].strip() + + if all([cpu_id, board_id, bios_id]): + print(f"\n方案一硬件信息:") + print(f"CPU ID: {cpu_id}") + print(f"主板序列号: {board_id}") + print(f"BIOS序列号: {bios_id}") + + combined = f"{cpu_id}:{board_id}:{bios_id}" + return hashlib.md5(combined.encode()).hexdigest() + except Exception as e: + print(f"方案一获取失败: {str(e)}") + return None + +def get_system_info(): + """获取系统信息(方案二)""" + startupinfo = None + if sys.platform == "win32": + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE + + backup_info = [] + + try: + # 获取系统盘序列号 + volume_info = subprocess.check_output('wmic logicaldisk where "DeviceID=\'C:\'" get VolumeSerialNumber', startupinfo=startupinfo).decode() + volume_serial = volume_info.split('\n')[1].strip() + if volume_serial: + backup_info.append(("volume", volume_serial)) + print(f"\n系统盘序列号: {volume_serial}") + except Exception as e: + print(f"获取系统盘序列号失败: {str(e)}") + + try: + # 获取Windows安装时间 + os_info = subprocess.check_output('wmic os get InstallDate', startupinfo=startupinfo).decode() + install_date = os_info.split('\n')[1].strip() + if install_date: + backup_info.append(("install", install_date)) + print(f"Windows安装时间: {install_date}") + except Exception as e: + print(f"获取系统安装时间失败: {str(e)}") + + if backup_info: + combined = "|".join(f"{k}:{v}" for k, v in sorted(backup_info)) + return hashlib.md5(combined.encode()).hexdigest() + return None + +def test_stability(): + """测试ID的稳定性""" + print("开始测试硬件ID稳定性...") + print("将进行10次测试,每次间隔1秒") + + # 存储每次生成的ID + hardware_ids = [] + system_ids = [] + + for i in range(10): + print(f"\n第 {i+1} 次测试:") + + # 测试方案一 + hw_id = get_hardware_info() + if hw_id: + print(f"方案一(硬件信息)生成的ID: {hw_id}") + hardware_ids.append(hw_id) + + # 测试方案二 + sys_id = get_system_info() + if sys_id: + print(f"方案二(系统信息)生成的ID: {sys_id}") + system_ids.append(sys_id) + + time.sleep(1) + + # 分析结果 + print("\n测试结果分析:") + + if hardware_ids: + unique_hw_ids = set(hardware_ids) + print(f"\n方案一(硬件信息):") + print(f"生成次数: {len(hardware_ids)}") + print(f"唯一ID数: {len(unique_hw_ids)}") + print("是否稳定: " + ("是" if len(unique_hw_ids) == 1 else "否")) + if len(unique_hw_ids) > 1: + print("出现的不同ID:") + for idx, id in enumerate(unique_hw_ids): + print(f"{idx+1}. {id}") + else: + print("\n方案一(硬件信息): 获取失败") + + if system_ids: + unique_sys_ids = set(system_ids) + print(f"\n方案二(系统信息):") + print(f"生成次数: {len(system_ids)}") + print(f"唯一ID数: {len(unique_sys_ids)}") + print("是否稳定: " + ("是" if len(unique_sys_ids) == 1 else "否")) + if len(unique_sys_ids) > 1: + print("出现的不同ID:") + for idx, id in enumerate(unique_sys_ids): + print(f"{idx+1}. {id}") + else: + print("\n方案二(系统信息): 获取失败") + +if __name__ == "__main__": + test_stability() \ No newline at end of file diff --git a/version.txt b/version.txt index e5b8203..80d13b7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.5.0 \ No newline at end of file +3.5.2 \ No newline at end of file