diff --git a/account_switcher.py b/account_switcher.py index 5909c35..4a915fa 100644 --- a/account_switcher.py +++ b/account_switcher.py @@ -352,106 +352,30 @@ class AccountSwitcher: return "网络请求失败,请稍后重试" return str(error) - def get_process_details(self, process_name: str) -> List[Dict]: - """获取进程详细信息 - - Args: - process_name: 进程名称 - - Returns: - List[Dict]: 进程详细信息列表 - """ - try: - # 使用 tasklist 命令替代 wmi - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - - output = subprocess.check_output( - f'tasklist /FI "IMAGENAME eq {process_name}" /FO CSV /NH', - startupinfo=startupinfo, - shell=True - ).decode('gbk') - - processes = [] - if output.strip(): - for line in output.strip().split('\n'): - if line.strip(): - parts = line.strip('"').split('","') - if len(parts) >= 2: - processes.append({ - 'name': parts[0], - 'pid': parts[1] - }) - return processes - except Exception as e: - logging.error(f"获取进程信息失败: {str(e)}") - return [] - def close_cursor_process(self) -> bool: - """关闭所有Cursor进程 - - Returns: - bool: 是否成功关闭所有进程 - """ + """关闭所有Cursor相关进程""" try: if sys.platform == "win32": - # 创建startupinfo对象来隐藏命令行窗口 startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = subprocess.SW_HIDE - # 获取进程详情 - processes = self.get_process_details("Cursor.exe") - if processes: - logging.info(f"发现 {len(processes)} 个Cursor进程") - for p in processes: - logging.info(f"进程信息: PID={p['pid']}, 路径={p['name']}") + # 直接使用taskkill命令强制结束所有相关进程 + process_names = ["Cursor.exe", "devsense.php.ls.exe", "intelliphp.ls.exe"] + for name in process_names: + try: + subprocess.run( + f"taskkill /f /t /im {name} >nul 2>&1", + startupinfo=startupinfo, + shell=True, + timeout=5 + ) + except: + pass - # 尝试关闭进程 - subprocess.run( - "taskkill /f /im Cursor.exe >nul 2>&1", - startupinfo=startupinfo, - shell=True - ) - - # 等待进程关闭,增加重试次数和等待时间 - retry_count = 0 - 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 >= 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}/{max_retries}...") - time.sleep(wait_time) - + # 等待一小段时间确保进程完全关闭 + time.sleep(2) return True else: - # 其他系统的处理 if sys.platform == "darwin": subprocess.run("killall Cursor 2>/dev/null", shell=True) else: @@ -472,12 +396,11 @@ class AccountSwitcher: return False # 2. 使用新的重置工具类执行重置 - success, message = self.resetter.reset_cursor(disable_update=True) + success, message = self.resetter.reset_cursor(disable_update=False) # 不在这里禁用更新 if not success: logging.error(f"重置失败: {message}") return False - # 不在这里重启Cursor,让调用者决定何时重启 logging.info("机器码重置完成") return True @@ -486,104 +409,55 @@ class AccountSwitcher: return False def restart_cursor(self) -> bool: - """重启Cursor编辑器 - - Returns: - bool: 是否成功重启 - """ + """重启Cursor编辑器""" try: - logging.info("正在重启Cursor...") + # 1. 确保进程已关闭 + self.close_cursor_process() - # 确保进程已关闭 - if not self.close_cursor_process(): - logging.error("无法关闭Cursor进程") - return False - - # 等待系统资源释放 - time.sleep(3) + # 2. 等待一小段时间 + time.sleep(2) - # 启动Cursor + # 3. 启动Cursor if sys.platform == "win32": cursor_exe = self.cursor_path / "Cursor.exe" if cursor_exe.exists(): - max_retries = 3 - for attempt in range(max_retries): - try: - # 使用subprocess启动 - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + try: + # 使用ShellExecuteW以管理员权限启动 + result = ctypes.windll.shell32.ShellExecuteW( + None, # 父窗口句柄 + "runas", # 以管理员权限运行 + str(cursor_exe), # 程序路径 + None, # 参数 + str(cursor_exe.parent), # 工作目录 + 1 # 正常窗口显示 + ) + + # ShellExecuteW返回值大于32表示成功 + if result > 32: + time.sleep(3) # 等待启动 + return True + else: + logging.error(f"启动Cursor失败,错误码: {result}") + return False - 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(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)}") + return False else: logging.error(f"未找到Cursor程序: {cursor_exe}") return False - elif sys.platform == "darwin": + else: try: - subprocess.run("open -a Cursor", shell=True, check=True) - time.sleep(5) - logging.info("Cursor启动成功") + if sys.platform == "darwin": + subprocess.run("open -a Cursor", shell=True, check=True) + else: + subprocess.run("sudo cursor &", shell=True, check=True) + time.sleep(3) return True - except subprocess.CalledProcessError as e: - logging.error(f"启动Cursor失败: {str(e)}") - return False - 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: + except Exception as e: logging.error(f"启动Cursor失败: {str(e)}") return False - return False - except Exception as e: logging.error(f"重启Cursor失败: {str(e)}") return False @@ -776,12 +650,14 @@ class AccountSwitcher: if not self.reset_machine_id(): return False, "重置机器码失败" - # 7. 重启Cursor(只在这里执行一次重启) + # 7. 重启Cursor logging.info("正在重启Cursor...") retry_count = 0 max_retries = 3 while retry_count < max_retries: if self.restart_cursor(): + # 等待进程完全启动 + time.sleep(5) break retry_count += 1 if retry_count < max_retries: @@ -824,50 +700,17 @@ class AccountSwitcher: """禁用Cursor更新""" 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) + if not self.close_cursor_process(): + return False, "无法关闭Cursor进程,请手动关闭后重试" - # 2. 删除updater目录并创建同名文件以阻止更新 - updater_path = Path(os.getenv('LOCALAPPDATA')) / "cursor-updater" + # 2. 调用resetter的disable_auto_update方法 + success, message = self.resetter.disable_auto_update() + if not success: + logging.error(f"禁用自动更新失败: {message}") + return False, f"禁用更新失败: {message}" - try: - # 如果是目录,则删除 - if updater_path.is_dir(): - import shutil - shutil.rmtree(str(updater_path)) - logging.info("删除updater目录成功") - - # 如果是文件,则删除 - if updater_path.is_file(): - os.remove(str(updater_path)) - logging.info("删除updater文件成功") - - # 创建同名空文件 - updater_path.touch() - logging.info("创建updater空文件成功") - - # 3. 重启Cursor - cursor_exe = self.cursor_path / "Cursor.exe" - if cursor_exe.exists(): - os.startfile(str(cursor_exe)) - logging.info("Cursor重启成功") - - return True, "Cursor更新已禁用,程序已重启" - - except Exception as e: - logging.error(f"操作updater文件失败: {str(e)}") - return False, f"禁用更新失败: {str(e)}" + logging.info("更新已禁用") + return True, "Cursor更新已禁用" except Exception as e: logging.error(f"禁用Cursor更新失败: {str(e)}") @@ -967,6 +810,26 @@ class AccountSwitcher: return False, "多次尝试后心跳发送失败" + def bypass_cursor_limit(self) -> Tuple[bool, str]: + """突破Cursor使用限制""" + try: + # 1. 先关闭所有Cursor进程 + if not self.close_cursor_process(): + return False, "无法关闭Cursor进程,请手动关闭后重试" + + # 2. 调用resetter的reset_cursor方法 + success, message = self.resetter.reset_cursor(disable_update=True) + if not success: + logging.error(f"突破限制失败: {message}") + return False, f"突破限制失败: {message}" + + logging.info("突破限制成功") + return True, "突破限制成功" + + except Exception as e: + logging.error(f"突破限制失败: {str(e)}") + return False, f"突破限制失败: {str(e)}" + def main(): """主函数""" try: @@ -982,13 +845,15 @@ def main(): switcher = AccountSwitcher() print("\n=== Cursor账号切换工具 ===") - print("1. 激活并切换账号") - print("2. 仅重置机器码") + print("1. 禁用自动更新") + print("2. 突破使用限制") + print("3. 激活并切换账号") + print("4. 仅重置机器码") while True: try: - choice = int(input("\n请选择操作 (1 或 2): ").strip()) - if choice in [1, 2]: + choice = int(input("\n请选择操作 (1-4): ").strip()) + if choice in [1, 2, 3, 4]: break else: print("无效的选项,请重新输入") @@ -996,6 +861,18 @@ def main(): print("请输入有效的数字") if choice == 1: + success, message = switcher.disable_cursor_update() + if success: + print("\n更新已禁用!") + else: + print(f"\n禁用更新失败: {message}") + elif choice == 2: + success, message = switcher.bypass_cursor_limit() + if success: + print("\n突破限制成功!") + else: + print(f"\n突破限制失败: {message}") + elif choice == 3: activation_code = input("请输入激活码: ").strip() if switcher.activate_and_switch(activation_code): print("\n账号激活成功!") diff --git a/banbenjietu.png b/banbenjietu.png index 9ae94f2..276042b 100644 Binary files a/banbenjietu.png and b/banbenjietu.png differ diff --git a/cursor_auth_manager.py b/cursor_auth_manager.py index f88dc52..d921a7d 100644 --- a/cursor_auth_manager.py +++ b/cursor_auth_manager.py @@ -348,27 +348,103 @@ class CursorAuthManager: # 确保进程已关闭 if not self.close_cursor_process(): + logging.error("无法关闭Cursor进程") return False + # 等待系统资源释放 + time.sleep(3) + # 启动Cursor if sys.platform == "win32": cursor_exe = self.cursor_path / "Cursor.exe" if cursor_exe.exists(): - os.startfile(str(cursor_exe)) - logging.info("Cursor启动成功") - return True + max_retries = 3 + for attempt in range(max_retries): + try: + # 检查是否还有Cursor进程在运行 + try: + subprocess.check_output( + "tasklist | findstr Cursor.exe", + startupinfo=subprocess.STARTUPINFO(), + shell=True + ) + # 如果找到进程,等待它关闭 + if attempt < max_retries - 1: + logging.info("等待进程完全关闭...") + time.sleep(3) + continue + else: + logging.error("无法确保所有Cursor进程已关闭") + return False + except subprocess.CalledProcessError: + # 没有找到进程,可以继续启动 + pass + + # 使用subprocess启动 + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + # 使用subprocess.Popen启动,并等待一段时间 + process = subprocess.Popen( + str(cursor_exe), + startupinfo=startupinfo, + creationflags=subprocess.CREATE_NEW_CONSOLE + ) + + # 等待进程启动 + time.sleep(5) + + # 验证进程是否成功启动 + if process.poll() is None: # 进程仍在运行 + 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)}") + return False else: logging.error(f"未找到Cursor程序: {cursor_exe}") return False elif sys.platform == "darwin": - subprocess.run("open -a Cursor", shell=True) - logging.info("Cursor启动成功") - return True + try: + subprocess.run("open -a Cursor", shell=True, check=True) + time.sleep(5) + logging.info("Cursor启动成功") + return True + except subprocess.CalledProcessError as e: + logging.error(f"启动Cursor失败: {str(e)}") + return False elif sys.platform == "linux": - subprocess.run("cursor &", shell=True) - logging.info("Cursor启动成功") - return True - + try: + subprocess.run("cursor &", shell=True, check=True) + time.sleep(5) + logging.info("Cursor启动成功") + return True + except subprocess.CalledProcessError as e: + logging.error(f"启动Cursor失败: {str(e)}") + return False + return False except Exception as e: diff --git a/cursor_win_id_modifier.ps1 b/cursor_win_id_modifier.ps1 index 4f94558..7277b8a 100644 --- a/cursor_win_id_modifier.ps1 +++ b/cursor_win_id_modifier.ps1 @@ -40,10 +40,9 @@ Write-Host @" "@ Write-Host "$BLUE================================$NC" -Write-Host "$GREEN Cursor 设备ID 修改工具 $NC" -Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】 $NC" -Write-Host "$YELLOW 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" -Write-Host "$YELLOW [重要提示] 本工具免费,如果对您有帮助,请关注公众号【煎饼果子卷AI】 $NC" +Write-Host "$GREEN 听泉Cursor助手 - 机器码重置工具 $NC" +Write-Host "$YELLOW 官方网站: cursor.nosqli.com $NC" +Write-Host "$YELLOW 微信客服: behikcigar $NC" Write-Host "$BLUE================================$NC" Write-Host "" @@ -191,26 +190,81 @@ $randomPart = Get-RandomHex -length 32 $MACHINE_ID = "$prefixHex$randomPart" $SQM_ID = "{$([System.Guid]::NewGuid().ToString().ToUpper())}" -# 在生成新ID后直接执行注册表操作,移除询问 +# 在Update-MachineGuid函数前添加权限检查 +if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + Write-Host "$RED[错误]$NC 请使用管理员权限运行此脚本" + Start-Process powershell "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + exit +} + function Update-MachineGuid { try { - $newMachineGuid = [System.Guid]::NewGuid().ToString() + # 先检查注册表路径是否存在 $registryPath = "HKLM:\SOFTWARE\Microsoft\Cryptography" + if (-not (Test-Path $registryPath)) { + throw "注册表路径不存在: $registryPath" + } + + # 获取当前的 MachineGuid + $currentGuid = Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop + if (-not $currentGuid) { + throw "无法获取当前的 MachineGuid" + } + + $originalGuid = $currentGuid.MachineGuid + Write-Host "$GREEN[信息]$NC 当前注册表值:" + Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" + Write-Host " MachineGuid REG_SZ $originalGuid" + + # 创建备份目录(如果不存在) + if (-not (Test-Path $BACKUP_DIR)) { + New-Item -ItemType Directory -Path $BACKUP_DIR -Force | Out-Null + } + + # 创建备份文件 + $backupFile = "$BACKUP_DIR\MachineGuid_$(Get-Date -Format 'yyyyMMdd_HHmmss').reg" + $backupResult = Start-Process "reg.exe" -ArgumentList "export", "`"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`"", "`"$backupFile`"" -NoNewWindow -Wait -PassThru - # 备份原始值 - $originalGuid = (Get-ItemProperty -Path $registryPath -Name "MachineGuid").MachineGuid - $backupFile = "$BACKUP_DIR\MachineGuid.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" - $originalGuid | Out-File $backupFile -Encoding UTF8 - + if ($backupResult.ExitCode -eq 0) { + Write-Host "$GREEN[信息]$NC 注册表项已备份到:$backupFile" + } else { + Write-Host "$YELLOW[警告]$NC 备份创建失败,继续执行..." + } + + # 生成新GUID + $newGuid = [System.Guid]::NewGuid().ToString() + # 更新注册表 - Set-ItemProperty -Path $registryPath -Name "MachineGuid" -Value $newMachineGuid - Write-Host "$GREEN[信息]$NC 已更新系统 MachineGuid: $newMachineGuid" - Write-Host "$GREEN[信息]$NC 原始值已备份至: $backupFile" - Write-Host "$GREEN[信息]$NC 注册表路径: 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" - Write-Host "$GREEN[信息]$NC 注册表项名: MachineGuid" + Set-ItemProperty -Path $registryPath -Name MachineGuid -Value $newGuid -Force -ErrorAction Stop + + # 验证更新 + $verifyGuid = (Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop).MachineGuid + if ($verifyGuid -ne $newGuid) { + throw "注册表验证失败:更新后的值 ($verifyGuid) 与预期值 ($newGuid) 不匹配" + } + + Write-Host "$GREEN[信息]$NC 注册表更新成功:" + Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" + Write-Host " MachineGuid REG_SZ $newGuid" + return $true } catch { - Write-Host "$RED[错误]$NC 更新系统 MachineGuid 失败: $_" + Write-Host "$RED[错误]$NC 注册表操作失败:$($_.Exception.Message)" + + # 尝试恢复备份 + if ($backupFile -and (Test-Path $backupFile)) { + Write-Host "$YELLOW[恢复]$NC 正在从备份恢复..." + $restoreResult = Start-Process "reg.exe" -ArgumentList "import", "`"$backupFile`"" -NoNewWindow -Wait -PassThru + + if ($restoreResult.ExitCode -eq 0) { + Write-Host "$GREEN[恢复成功]$NC 已还原原始注册表值" + } else { + Write-Host "$RED[错误]$NC 恢复失败,请手动导入备份文件:$backupFile" + } + } else { + Write-Host "$YELLOW[警告]$NC 未找到备份文件或备份创建失败,无法自动恢复" + } + return $false } } @@ -297,7 +351,7 @@ try { # 显示公众号信息 Write-Host "" Write-Host "$GREEN================================$NC" - Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" + Write-Host "$YELLOW 欢迎使用Cursor助手 $NC" Write-Host "$GREEN================================$NC" Write-Host "" Write-Host "$GREEN[信息]$NC 请重启 Cursor 以应用新的配置" @@ -413,6 +467,9 @@ try { Write-Host "$GREEN[信息]$NC 保持默认设置,不进行更改" } + # 保留有效的注册表更新 + Update-MachineGuid + } catch { Write-Host "$RED[错误]$NC 主要操作失败: $_" Write-Host "$YELLOW[尝试]$NC 使用备选方法..." diff --git a/disable_update.ps1 b/disable_update.ps1 new file mode 100644 index 0000000..1922ef0 --- /dev/null +++ b/disable_update.ps1 @@ -0,0 +1,83 @@ +# 检查管理员权限 +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + Write-Host "`n[错误] 请以管理员身份运行此脚本" -ForegroundColor Red + Write-Host "请使用 'Start-Process pwsh -Verb RunAs' 启动管理员PowerShell" -ForegroundColor Yellow + Pause + exit +} + +Write-Host "`n=== Cursor更新禁用工具 ===" -ForegroundColor Cyan + +# 定义路径 +$updaterPath = Join-Path $env:LOCALAPPDATA "cursor-updater" +$cursorPath = Join-Path $env:LOCALAPPDATA "Programs\cursor" +$packageJsonPath = Join-Path $cursorPath "resources\app\package.json" + +Write-Host "`n正在检查路径..." -ForegroundColor Gray +Write-Host "updater路径: $updaterPath" -ForegroundColor Gray + +try { + # 1. 删除现有的updater文件/目录 + if (Test-Path $updaterPath) { + Write-Host "`n发现现有updater文件/目录,正在删除..." -ForegroundColor Yellow + Remove-Item -Path $updaterPath -Force -Recurse -ErrorAction Stop + Write-Host "成功删除现有文件/目录" -ForegroundColor Green + } + + # 2. 创建空文件 + Write-Host "`n正在创建updater文件..." -ForegroundColor Yellow + New-Item -Path $updaterPath -ItemType File -Force -ErrorAction Stop | Out-Null + Write-Host "成功创建updater文件" -ForegroundColor Green + + # 3. 设置文件权限 + Write-Host "`n正在设置文件权限..." -ForegroundColor Yellow + + # 获取当前用户 + $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + + # 创建新的ACL + $acl = New-Object System.Security.AccessControl.FileSecurity + $acl.SetAccessRuleProtection($true, $false) # 禁用继承 + + # 添加只读权限规则 + $readRule = New-Object System.Security.AccessControl.FileSystemAccessRule( + $currentUser, + [System.Security.AccessControl.FileSystemRights]::Read, + [System.Security.AccessControl.AccessControlType]::Allow + ) + $acl.AddAccessRule($readRule) + + # 应用ACL + Set-Acl -Path $updaterPath -AclObject $acl -ErrorAction Stop + + # 设置只读属性 + Set-ItemProperty -Path $updaterPath -Name IsReadOnly -Value $true -ErrorAction Stop + Write-Host "成功设置文件权限" -ForegroundColor Green + + # 4. 修改package.json + if (Test-Path $packageJsonPath) { + Write-Host "`n正在修改package.json..." -ForegroundColor Yellow + $json = Get-Content $packageJsonPath -Raw | ConvertFrom-Json + $json.updateUrl = "" + $json.disableUpdate = $true + $json | ConvertTo-Json -Depth 10 | Set-Content $packageJsonPath -Encoding UTF8 + Write-Host "成功修改package.json" -ForegroundColor Green + } + + # 5. 验证权限 + Write-Host "`n正在验证文件权限..." -ForegroundColor Yellow + $item = Get-Item $updaterPath + if (-not $item.IsReadOnly) { + throw "文件权限验证失败:文件不是只读" + } + Write-Host "文件权限验证通过" -ForegroundColor Green + + Write-Host "`n[成功] Cursor更新已禁用!" -ForegroundColor Green + +} catch { + Write-Host "`n[错误] 操作失败: $($_.Exception.Message)" -ForegroundColor Red +} + +Write-Host "`n按任意键退出..." -ForegroundColor Gray +$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") \ No newline at end of file diff --git a/gui/main_window.py b/gui/main_window.py index 1e66860..731b358 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -6,9 +6,9 @@ from PIL import Image from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFrame, QTextEdit, QMessageBox, QApplication, QSystemTrayIcon, QMenu, - QDialog, QProgressBar, QStyle) + QDialog, QProgressBar, QStyle, QDialogButtonBox, QToolTip) from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal -from PyQt5.QtGui import QIcon, QPixmap +from PyQt5.QtGui import QIcon, QPixmap, QCursor import time import requests from urllib.parse import quote @@ -608,6 +608,30 @@ class MainWindow(QMainWindow): disable_update_btn.setMinimumWidth(500) btn_layout.addWidget(disable_update_btn, 0, Qt.AlignCenter) + # 重启Cursor按钮 + self.restart_cursor_btn = QPushButton("重启Cursor") + self.restart_cursor_btn.setFixedHeight(30) + self.restart_cursor_btn.setStyleSheet(""" + QPushButton { + background-color: #2d8cf0; + border: none; + color: white; + padding: 5px 15px; + border-radius: 4px; + font-size: 13px; + } + QPushButton:hover { + background-color: #2b85e4; + } + QPushButton:pressed { + background-color: #2979d9; + } + QPushButton:disabled { + background-color: #bbbec4; + } + """) + self.restart_cursor_btn.clicked.connect(self.restart_cursor) + main_layout.addWidget(btn_frame) # 检查更新按钮 @@ -1336,91 +1360,11 @@ class MainWindow(QMainWindow): try: # 显示加载对话框 - self.show_loading_dialog("正在禁用更新,请稍候...") + 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 = ApiWorker(self.switcher.disable_cursor_update) + self.worker.finished.connect(self.on_disable_update_complete) self.worker.start() except Exception as e: @@ -1429,167 +1373,243 @@ class MainWindow(QMainWindow): 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)) + """禁用更新完成的回调""" + try: + success, data = result + logging.info(f"禁用更新操作结果: success={success}, data={data}") + + self.hide_loading_dialog() + self._request_complete() + + if isinstance(data, tuple): + inner_success, inner_message = data + if inner_success: + self.show_custom_message( + "成功", + "禁用更新成功", + inner_message, + QMessageBox.Information, + "#198754" + ) + QTimer.singleShot(1000, self.check_status) + else: + # 创建自定义对话框 + dialog = QDialog(self) + dialog.setWindowTitle("禁用更新失败") + dialog.setFixedWidth(400) + + layout = QVBoxLayout() + + # 警告图标和标题 + header_layout = QHBoxLayout() + warning_icon = QLabel() + warning_icon.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(32, 32)) + header_layout.addWidget(warning_icon) + header_label = QLabel("需要手动操作") + header_label.setStyleSheet("color: red; font-size: 16px; font-weight: bold;") + header_layout.addWidget(header_label) + header_layout.addStretch() + layout.addLayout(header_layout) + + # 分隔线 + line = QFrame() + line.setFrameShape(QFrame.HLine) + line.setFrameShadow(QFrame.Sunken) + layout.addWidget(line) + + # 手动步骤说明 + steps_label = QLabel( + "请按以下步骤手动操作:\n\n" + "1. 按下 Win + R 组合键\n" + "2. 在运行框中输入 powershell 或 pwsh\n" + "3. 按 Ctrl + Shift + Enter 以管理员身份运行\n" + "4. 在管理员终端中输入以下命令:" + ) + steps_label.setWordWrap(True) + steps_label.setStyleSheet("margin: 10px 0;") + layout.addWidget(steps_label) + + # 命令文本框和复制按钮 + command_layout = QHBoxLayout() + command_text = QLineEdit() + command_text.setText("irm https://github.com/maticarmy/cursor-nosqli-tools/blob/main/scripts/run/cursor_win_id_modifier.ps1 | iex") + command_text.setReadOnly(True) + command_layout.addWidget(command_text) + + copy_button = QPushButton("复制") + copy_button.setStyleSheet(""" + QPushButton { + background-color: #007bff; + color: white; + border: none; + padding: 5px 15px; + border-radius: 3px; + } + QPushButton:hover { + background-color: #0056b3; + } + """) + copy_button.clicked.connect(lambda: self.copy_and_show_tip(command_text, command_text.text(), "命令已复制到剪贴板")) + command_layout.addWidget(copy_button) + layout.addLayout(command_layout) + + # 按钮 + button_box = QDialogButtonBox(QDialogButtonBox.Ok) + button_box.accepted.connect(dialog.accept) + layout.addWidget(button_box) + + dialog.setLayout(layout) + dialog.exec_() + else: + self.show_custom_error("禁用更新失败", str(data)) + + except Exception as e: + logging.error(f"处理禁用更新回调时发生错误: {str(e)}") + self.show_custom_error("错误", "处理结果时发生错误,请重试") def bypass_cursor_limit(self): """突破Cursor版本限制""" if not self.check_activation_status(): return - if not self._check_request_throttle(): - return - + # 显示加载对话框 + self.show_loading_dialog("正在突破限制...") + + # 创建工作线程 + self.worker = ApiWorker(self.switcher.bypass_cursor_limit) + self.worker.finished.connect(self.on_bypass_limit_complete) + self.worker.start() + + def on_bypass_limit_complete(self, result): + """突破限制完成回调""" try: - # 显示加载对话框 - self.show_loading_dialog("正在突破版本限制,请稍候...") + success, data = result + logging.info(f"突破限制操作结果: success={success}, data={data}") - # 创建工作线程 - from utils.cursor_registry import CursorRegistry - registry = CursorRegistry() + # 确保在主线程中执行 UI 操作 + self.hide_loading_dialog() + self._request_complete() - def reset_func(): - try: - # 1. 先关闭所有Cursor进程 - if sys.platform == "win32": - os.system("taskkill /f /im Cursor.exe >nul 2>&1") - time.sleep(2) + if isinstance(data, tuple): + inner_success, inner_message = data + if inner_success: + try: + logging.info("准备显示成功消息对话框") + self.show_custom_message( + "成功", + "突破限制成功", + inner_message, + QStyle.SP_DialogApplyButton, + "#198754" + ) + logging.info("成功消息对话框显示完成") + + # 更新状态显示 + QTimer.singleShot(1000, self.check_status) + logging.info("已安排状态更新") + except Exception as e: + logging.error(f"显示成功消息时发生错误: {str(e)}") + # 使用更简单的消息框作为后备方案 + QMessageBox.information(self, "成功", "突破限制成功") + else: + try: + logging.info("准备显示错误消息对话框") + self.show_custom_error("突破限制失败", str(inner_message)) + logging.info("错误消息对话框显示完成") + except Exception as e: + logging.error(f"显示错误消息时发生错误: {str(e)}") + # 使用更简单的消息框作为后备方案 + QMessageBox.critical(self, "错误", f"突破限制失败: {str(inner_message)}") + else: + self.show_custom_error("突破限制失败", str(data)) - # 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) + except Exception as e: + logging.error(f"处理突破限制回调时发生错误: {str(e)}") + try: + self.hide_loading_dialog() + self._request_complete() + QMessageBox.critical(self, "错误", "处理结果时发生错误,请重试") + except: + pass + + def show_custom_message(self, title, header, message, icon_type, color, show_copy_button=False, copy_text=None): + """显示自定义消息对话框 + Args: + show_copy_button: 是否显示复制按钮 + copy_text: 要复制的文本内容 + """ + try: + logging.info(f"准备显示自定义消息框: {header}") - self.worker = ApiWorker(reset_func) - self.worker.finished.connect(self.on_bypass_complete) - self.worker.start() + dialog = QDialog(self) + dialog.setWindowTitle(title) + dialog.setFixedWidth(500) + + layout = QVBoxLayout() + + # 标题标签 + header_label = QLabel(header) + header_label.setStyleSheet(f"color: {color}; font-size: 16px; font-weight: bold;") + layout.addWidget(header_label) + + # 消息文本 + message_label = QLabel(message) + message_label.setWordWrap(True) + message_label.setStyleSheet("margin: 10px 0;") + layout.addWidget(message_label) + + # 添加复制按钮 + if show_copy_button and copy_text: + copy_button = QPushButton("复制命令") + copy_button.setStyleSheet(""" + QPushButton { + background-color: #007bff; + color: white; + border: none; + padding: 5px 15px; + border-radius: 3px; + } + QPushButton:hover { + background-color: #0056b3; + } + """) + copy_button.clicked.connect(lambda: self.copy_to_clipboard(copy_text)) + layout.addWidget(copy_button, alignment=Qt.AlignCenter) + + # 确定按钮 + button_box = QDialogButtonBox(QDialogButtonBox.Ok) + button_box.accepted.connect(dialog.accept) + layout.addWidget(button_box) + + dialog.setLayout(layout) + dialog.exec_() + logging.info("自定义消息框已关闭") 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)) + logging.error(f"显示自定义消息框时发生错误: {str(e)}") + QMessageBox.critical(self, title, message) + + def copy_to_clipboard(self, text): + """复制文本到剪贴板""" + clipboard = QApplication.clipboard() + clipboard.setText(text) + QToolTip.showText(QCursor.pos(), "已复制到剪贴板", None, 2000) - 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" - ) + try: + logging.info(f"准备显示错误消息框: {header}") + self.show_custom_message( + "错误", + header, + message, + QStyle.SP_MessageBoxCritical, + "#dc3545" + ) + logging.info("错误消息框显示完成") + except Exception as e: + logging.error(f"显示自定义错误消息框失败: {str(e)}") + QMessageBox.critical(self, "错误", f"{header}\n\n{message}") def _check_request_throttle(self) -> bool: """检查是否可以发送请求(防重复提交) @@ -2028,4 +2048,55 @@ class MainWindow(QMainWindow): # 更新状态显示 self.check_status() else: - logging.error(f"心跳发送失败: {message}") \ No newline at end of file + logging.error(f"心跳发送失败: {message}") + + def restart_cursor(self): + """重启Cursor进程""" + try: + self.restart_cursor_btn.setEnabled(False) + self.show_loading_dialog("正在重启Cursor...") + + def restart_func(): + try: + # 先关闭进程 + if not self.switcher.close_cursor_process(): + return False, "无法关闭Cursor进程,请手动关闭后重试" + + # 等待资源释放 + time.sleep(2) + + # 重启Cursor + if not self.switcher.restart_cursor(): + return False, "重启Cursor失败,请手动启动Cursor" + + return True, "Cursor重启成功" + except Exception as e: + logging.error(f"重启Cursor失败: {str(e)}") + return False, f"重启失败: {str(e)}" + + # 创建工作线程 + worker = ApiWorker(restart_func) + worker.finished.connect(self.on_restart_complete) + worker.start() + + except Exception as e: + self.restart_cursor_btn.setEnabled(True) + self.hide_loading_dialog() + self.show_custom_error("重启失败", str(e)) + + def on_restart_complete(self, result): + """重启完成的回调函数""" + success, message = result + self.restart_cursor_btn.setEnabled(True) + self.hide_loading_dialog() + + if success: + self.show_custom_message( + "重启成功", + "Cursor已重启", + message, + QMessageBox.Information, + "#2d8cf0" + ) + else: + self.show_custom_error("重启失败", message) \ No newline at end of file diff --git a/test_disable_update.py b/test_disable_update.py new file mode 100644 index 0000000..b39930a --- /dev/null +++ b/test_disable_update.py @@ -0,0 +1,210 @@ +import os +import sys +import json +import ctypes +import logging +import subprocess +from pathlib import Path +from datetime import datetime +from typing import Optional, Tuple, Dict + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) + +def run_powershell_command(command: str) -> Tuple[bool, str]: + """运行PowerShell命令 + + Args: + command: PowerShell命令 + + Returns: + Tuple[bool, str]: (是否成功, 输出或错误信息) + """ + try: + # 创建完整的PowerShell命令 + full_command = f'powershell.exe -Command "{command}"' + + # 运行命令 + result = subprocess.run( + full_command, + capture_output=True, + text=True, + shell=True + ) + + if result.returncode == 0: + return True, result.stdout.strip() + else: + return False, result.stderr.strip() + except Exception as e: + return False, str(e) + +def is_admin(): + """检查是否具有管理员权限""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + +def run_as_admin(): + """以管理员权限重新运行程序""" + try: + if not is_admin(): + # 获取当前脚本的路径 + script = sys.argv[0] + params = ' '.join(sys.argv[1:]) + + # 以管理员权限重新运行 + ctypes.windll.shell32.ShellExecuteW( + None, + "runas", + sys.executable, + f'"{script}" {params}', + None, + 1 + ) + return True + except Exception as e: + logging.error(f"以管理员权限运行失败: {str(e)}") + return False + +class CursorUpdateDisabler: + """专门用于测试禁用Cursor更新的类""" + + def __init__(self): + self.localappdata = os.getenv('LOCALAPPDATA') + self.cursor_path = Path(self.localappdata) / "Programs" / "cursor" + self.app_path = self.cursor_path / "resources" / "app" + self.package_json = self.app_path / "package.json" + self.updater_path = Path(self.localappdata) / "cursor-updater" + + def disable_auto_update(self) -> Tuple[bool, str]: + """禁用自动更新 + + Returns: + Tuple[bool, str]: (是否成功, 消息) + """ + try: + logging.info(f"开始禁用更新操作...") + logging.info(f"updater路径: {self.updater_path}") + + # 1. 使用PowerShell强制删除现有文件/目录 + if self.updater_path.exists(): + logging.info("发现现有updater文件/目录,尝试强制删除...") + + # 先获取完全控制权限 + take_control_cmd = f'$path = "{self.updater_path}"; $acl = Get-Acl $path; $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name; $fileSystemRights = [System.Security.AccessControl.FileSystemRights]::FullControl; $type = [System.Security.AccessControl.AccessControlType]::Allow; $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $type); $acl.SetAccessRule($rule); Set-Acl -Path $path -AclObject $acl' + success, output = run_powershell_command(take_control_cmd) + if not success: + logging.warning(f"设置完全控制权限失败: {output}") + + # 强制删除 + remove_cmd = f'Remove-Item -Path "{self.updater_path}" -Force -Recurse' + success, output = run_powershell_command(remove_cmd) + if not success: + logging.error(f"强制删除失败: {output}") + return False, f"删除现有文件失败: {output}" + logging.info("成功删除现有文件/目录") + + # 2. 创建空文件 + try: + with open(self.updater_path, 'w') as f: + pass + logging.info("成功创建updater空文件") + except Exception as e: + logging.error(f"创建updater文件失败: {str(e)}") + return False, f"创建updater文件失败: {str(e)}" + + # 3. 设置文件权限 + try: + # 设置文件为只读并禁止修改 + protect_cmd = f'$path = "{self.updater_path}"; $acl = Get-Acl $path; $acl.SetAccessRuleProtection($true, $false); $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name; $fileSystemRights = [System.Security.AccessControl.FileSystemRights]::Read; $type = [System.Security.AccessControl.AccessControlType]::Allow; $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $type); $acl.AddAccessRule($rule); Set-Acl -Path $path -AclObject $acl' + success, output = run_powershell_command(protect_cmd) + if not success: + logging.error(f"设置文件权限失败: {output}") + return False, f"设置文件权限失败: {output}" + logging.info("成功设置文件权限") + + # 设置文件为只读 + os.chmod(str(self.updater_path), 0o444) # 设置为只读 + logging.info("成功设置文件只读属性") + except Exception as e: + logging.error(f"设置文件权限失败: {str(e)}") + return False, f"设置文件权限失败: {str(e)}" + + # 4. 修改package.json + try: + if self.package_json.exists(): + with open(self.package_json, "r", encoding="utf-8") as f: + data = json.load(f) + + data["updateUrl"] = "" + data["disableUpdate"] = True + + with open(self.package_json, "w", encoding="utf-8", newline='\n') as f: + json.dump(data, f, indent=2) + logging.info("成功修改package.json配置") + except Exception as e: + logging.warning(f"修改package.json失败: {str(e)}") + + # 5. 验证文件权限 + try: + if not self.updater_path.exists(): + return False, "updater文件不存在" + + # 验证文件是否为只读 + if os.access(str(self.updater_path), os.W_OK): + logging.error("文件权限验证失败:文件可写") + return False, "文件权限设置失败:文件仍然可写" + + logging.info("文件权限验证通过") + except Exception as e: + logging.error(f"验证文件权限失败: {str(e)}") + return False, f"验证文件权限失败: {str(e)}" + + logging.info("禁用自动更新成功完成") + return True, "Cursor更新已禁用" + + except Exception as e: + error_msg = f"禁用自动更新失败: {str(e)}" + logging.error(error_msg) + return False, error_msg + +def main(): + """主函数""" + try: + # 检查管理员权限 + if os.name == 'nt': + import ctypes + if not is_admin(): + logging.warning("当前不是管理员权限运行") + print("\n[错误] 请按以下步骤手动操作:") + print("1. 按下 Win + R 组合键") + print("2. 在运行框中输入 powershell 或 pwsh") + print("3. 按 Ctrl + Shift + Enter 以管理员身份运行") + print("4. 在管理员终端中输入以下命令:") + print(" irm https://github.com/maticarmy/cursor-nosqli-tools/blob/main/scripts/run/cursor_win_id_modifier.ps1 | iex") + input("\n按回车键退出...") + return + + print("\n=== Cursor更新禁用测试工具 ===") + disabler = CursorUpdateDisabler() + success, message = disabler.disable_auto_update() + + if success: + print("\n更新已禁用!") + else: + print(f"\n禁用更新失败: {message}") + + except Exception as e: + logging.error(f"程序执行出错: {str(e)}") + print(f"\n程序执行出错: {str(e)}") + + finally: + input("\n按回车键退出...") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/utils/cursor_resetter.py b/utils/cursor_resetter.py index 48b1d22..e08e975 100644 --- a/utils/cursor_resetter.py +++ b/utils/cursor_resetter.py @@ -143,38 +143,101 @@ class CursorResetter: logging.error(f"更新配置文件失败: {str(e)}") return False - def disable_auto_update(self) -> bool: - """禁用自动更新""" + def disable_auto_update(self) -> Tuple[bool, str]: + """禁用自动更新 + + Returns: + Tuple[bool, str]: (是否成功, 消息) + """ try: updater_path = Path(self.localappdata) / "cursor-updater" - # 删除现有文件/目录 - if updater_path.exists(): - if updater_path.is_dir(): - import shutil - shutil.rmtree(updater_path) - else: - updater_path.unlink() + # 1. 先尝试删除现有文件/目录 + try: + if updater_path.exists(): + if updater_path.is_dir(): + import shutil + shutil.rmtree(str(updater_path)) + logging.info("已删除updater目录") + else: + updater_path.unlink() + logging.info("已删除updater文件") + except Exception as e: + logging.warning(f"删除现有updater文件/目录失败: {str(e)}") - # 创建空文件并设置只读 - updater_path.touch() - import stat - updater_path.chmod(stat.S_IREAD) - - # 设置文件权限 - if os.name == 'nt': - subprocess.run( - f'icacls "{updater_path}" /inheritance:r /grant:r "{os.getenv("USERNAME")}:(R)"', - shell=True, - check=True - ) + # 2. 创建空文件 + try: + # 如果文件已存在,先移除它 + if updater_path.exists(): + try: + os.remove(str(updater_path)) + except: + pass + # 创建空文件 + with open(updater_path, 'w') as f: + pass + logging.info("已创建updater空文件") + except Exception as e: + logging.error(f"创建updater文件失败: {str(e)}") + return False, f"创建updater文件失败: {str(e)}" + + # 3. 设置文件权限 + try: + import stat + # 设置文件为只读 + os.chmod(str(updater_path), stat.S_IREAD) + logging.info("已设置只读属性") + + if os.name == 'nt': + # 使用takeown获取文件所有权 + subprocess.run(['takeown', '/f', str(updater_path)], check=True, capture_output=True) + logging.info("已获取文件所有权") + + # 使用icacls设置权限 + username = os.getenv("USERNAME") + subprocess.run( + ['icacls', str(updater_path), '/inheritance:r', '/grant:r', f'{username}:(R)'], + check=True, + capture_output=True + ) + logging.info("已设置文件权限") + except Exception as e: + logging.error(f"设置文件权限失败: {str(e)}") + return False, f"设置文件权限失败: {str(e)}" + + # 4. 修改package.json + try: + if self.package_json.exists(): + with open(self.package_json, "r", encoding="utf-8") as f: + data = json.load(f) + + data["updateUrl"] = "" + data["disableUpdate"] = True + + with open(self.package_json, "w", encoding="utf-8", newline='\n') as f: + json.dump(data, f, indent=2) + logging.info("已修改package.json配置") + except Exception as e: + logging.warning(f"修改package.json失败: {str(e)}") + + # 5. 验证文件权限 + try: + if not updater_path.exists(): + return False, "updater文件不存在" + if os.access(str(updater_path), os.W_OK): + return False, "文件权限设置失败" + except Exception as e: + logging.error(f"验证文件权限失败: {str(e)}") + return False, f"验证文件权限失败: {str(e)}" + logging.info("已禁用自动更新") - return True + return True, "Cursor更新已禁用" except Exception as e: - logging.error(f"禁用自动更新失败: {str(e)}") - return False + error_msg = f"禁用自动更新失败: {str(e)}" + logging.error(error_msg) + return False, error_msg def reset_cursor(self, disable_update: bool = True) -> Tuple[bool, str]: """重置Cursor @@ -201,8 +264,7 @@ class CursorResetter: return False, "更新系统MachineGuid失败" # 4. 禁用自动更新(如果需要) - if disable_update and not self.disable_auto_update(): - logging.warning("禁用自动更新失败") + # 5. 修改package.json if self.package_json.exists(): diff --git a/version.txt b/version.txt index 80d13b7..678fd88 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.5.2 \ No newline at end of file +3.5.3 \ No newline at end of file