mirror of
https://github.com/yuaotian/go-cursor-help.git
synced 2025-08-02 22:07:36 +08:00

- Added new messages for process management, including checking and closing Cursor instances, to improve user feedback. - Introduced new fields in the TextResource struct for better multilingual support, enhancing user experience across different languages. - Updated the ensureCursorClosed function to provide clearer status messages during the process of closing instances. - Improved the overall structure and readability of the code related to process management. These changes collectively enhance the application's usability and provide clearer communication to users during operation.
953 lines
25 KiB
Go
953 lines
25 KiB
Go
package main
|
||
|
||
// Core imports / 核心导入
|
||
import (
|
||
"bufio"
|
||
"context"
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"os/exec"
|
||
"os/user"
|
||
"path/filepath"
|
||
"runtime"
|
||
"runtime/debug"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/fatih/color"
|
||
)
|
||
|
||
// Types and Constants / 类型和常量
|
||
type Language string
|
||
|
||
const (
|
||
// Language options / 语言选项
|
||
CN Language = "cn"
|
||
EN Language = "en"
|
||
|
||
// Version / 版本号
|
||
Version = "1.0.1"
|
||
|
||
// Error types / 错误类型
|
||
ErrPermission = "permission_error"
|
||
ErrConfig = "config_error"
|
||
ErrProcess = "process_error"
|
||
ErrSystem = "system_error"
|
||
)
|
||
|
||
// Configuration Structures / 配置结构
|
||
type (
|
||
// TextResource stores multilingual text / 存储多语言文本
|
||
TextResource struct {
|
||
SuccessMessage string
|
||
RestartMessage string
|
||
ReadingConfig string
|
||
GeneratingIds string
|
||
PressEnterToExit string
|
||
ErrorPrefix string
|
||
PrivilegeError string
|
||
RunAsAdmin string
|
||
RunWithSudo string
|
||
SudoExample string
|
||
ConfigLocation string
|
||
CheckingProcesses string
|
||
ClosingProcesses string
|
||
ProcessesClosed string
|
||
PleaseWait string
|
||
}
|
||
|
||
// StorageConfig optimized storage configuration / 优化的存储配置
|
||
StorageConfig struct {
|
||
TelemetryMacMachineId string `json:"telemetry.macMachineId"`
|
||
TelemetryMachineId string `json:"telemetry.machineId"`
|
||
TelemetryDevDeviceId string `json:"telemetry.devDeviceId"`
|
||
TelemetrySqmId string `json:"telemetry.sqmId"` // Added TelemetrySqmId
|
||
LastModified time.Time `json:"lastModified"`
|
||
Version string `json:"version"`
|
||
}
|
||
// AppError defines error types / 定义错误类型
|
||
AppError struct {
|
||
Type string
|
||
Op string
|
||
Path string
|
||
Err error
|
||
Context map[string]interface{}
|
||
}
|
||
|
||
// Config structures / 配置结构
|
||
Config struct {
|
||
Storage StorageConfig
|
||
UI UIConfig
|
||
System SystemConfig
|
||
}
|
||
|
||
UIConfig struct {
|
||
Language Language
|
||
Theme string
|
||
Spinner SpinnerConfig
|
||
}
|
||
|
||
SystemConfig struct {
|
||
RetryAttempts int
|
||
RetryDelay time.Duration
|
||
Timeout time.Duration
|
||
}
|
||
|
||
// SpinnerConfig defines spinner configuration / 定义进度条配置
|
||
SpinnerConfig struct {
|
||
Frames []string
|
||
Delay time.Duration
|
||
}
|
||
|
||
// ProgressSpinner for showing progress animation / 用于显示进度动画
|
||
ProgressSpinner struct {
|
||
frames []string
|
||
current int
|
||
message string
|
||
}
|
||
)
|
||
|
||
// Global Variables / 全局变量
|
||
var (
|
||
currentLanguage = CN // Default to Chinese / 默认为中文
|
||
|
||
texts = map[Language]TextResource{
|
||
CN: {
|
||
SuccessMessage: "[√] 配置文件已成功更新!",
|
||
RestartMessage: "[!] 请手动重启 Cursor 以使更新生效",
|
||
ReadingConfig: "正在读取配置文件...",
|
||
GeneratingIds: "正在生成新的标识符...",
|
||
PressEnterToExit: "按回车键退出程序...",
|
||
ErrorPrefix: "程序发生严重错误: %v",
|
||
PrivilegeError: "\n[!] 错误:需要管理员权限",
|
||
RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」",
|
||
RunWithSudo: "请使用 sudo 命令运行此程序",
|
||
SudoExample: "示例: sudo %s",
|
||
ConfigLocation: "配置文件位置:",
|
||
CheckingProcesses: "正在检查运行中的 Cursor 实例...",
|
||
ClosingProcesses: "正在关闭 Cursor 实例...",
|
||
ProcessesClosed: "所有 Cursor 实例已关闭",
|
||
PleaseWait: "请稍候...",
|
||
},
|
||
EN: {
|
||
SuccessMessage: "[√] Configuration file updated successfully!",
|
||
RestartMessage: "[!] Please restart Cursor manually for changes to take effect",
|
||
ReadingConfig: "Reading configuration file...",
|
||
GeneratingIds: "Generating new identifiers...",
|
||
PressEnterToExit: "Press Enter to exit...",
|
||
ErrorPrefix: "Program encountered a serious error: %v",
|
||
PrivilegeError: "\n[!] Error: Administrator privileges required",
|
||
RunAsAdmin: "Please right-click and select 'Run as Administrator'",
|
||
RunWithSudo: "Please run this program with sudo",
|
||
SudoExample: "Example: sudo %s",
|
||
ConfigLocation: "Config file location:",
|
||
CheckingProcesses: "Checking for running Cursor instances...",
|
||
ClosingProcesses: "Closing Cursor instances...",
|
||
ProcessesClosed: "All Cursor instances have been closed",
|
||
PleaseWait: "Please wait...",
|
||
},
|
||
}
|
||
)
|
||
|
||
// Error Implementation / 错误实现
|
||
func (e *AppError) Error() string {
|
||
if e.Context != nil {
|
||
return fmt.Sprintf("[%s] %s: %v (context: %v)", e.Type, e.Op, e.Err, e.Context)
|
||
}
|
||
return fmt.Sprintf("[%s] %s: %v", e.Type, e.Op, e.Err)
|
||
}
|
||
|
||
// Configuration Functions / 配置函数
|
||
func NewStorageConfig(oldConfig *StorageConfig) *StorageConfig { // Modified to take old config
|
||
newConfig := &StorageConfig{
|
||
TelemetryMacMachineId: generateMachineId(),
|
||
TelemetryMachineId: generateMachineId(),
|
||
TelemetryDevDeviceId: generateDevDeviceId(),
|
||
LastModified: time.Now(),
|
||
Version: Version,
|
||
}
|
||
|
||
if oldConfig != nil {
|
||
newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId
|
||
} else {
|
||
newConfig.TelemetrySqmId = generateMachineId()
|
||
}
|
||
|
||
if newConfig.TelemetrySqmId == "" {
|
||
newConfig.TelemetrySqmId = generateMachineId()
|
||
}
|
||
|
||
return newConfig
|
||
}
|
||
|
||
// ID Generation Functions / ID生成函数
|
||
func generateMachineId() string {
|
||
data := make([]byte, 32)
|
||
if _, err := rand.Read(data); err != nil {
|
||
panic(fmt.Errorf("failed to generate random data: %v", err))
|
||
}
|
||
hash := sha256.Sum256(data)
|
||
return hex.EncodeToString(hash[:])
|
||
}
|
||
|
||
func generateDevDeviceId() string {
|
||
uuid := make([]byte, 16)
|
||
if _, err := rand.Read(uuid); err != nil {
|
||
panic(fmt.Errorf("failed to generate UUID: %v", err))
|
||
}
|
||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||
return fmt.Sprintf("%x-%x-%x-%x-%x",
|
||
uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16])
|
||
}
|
||
|
||
// File Operations / 文件操作
|
||
func getConfigPath(username string) (string, error) { // Modified to take username
|
||
var configDir string
|
||
switch runtime.GOOS {
|
||
case "windows":
|
||
configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage")
|
||
case "darwin":
|
||
homeDir := filepath.Join("/home/", username)
|
||
configDir = filepath.Join(homeDir, "Library", "Application Support", "Cursor", "User", "globalStorage")
|
||
case "linux":
|
||
homeDir := filepath.Join("/home/", username)
|
||
configDir = filepath.Join(homeDir, ".config", "Cursor", "User", "globalStorage")
|
||
default:
|
||
return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||
}
|
||
return filepath.Join(configDir, "storage.json"), nil
|
||
}
|
||
|
||
func saveConfig(config *StorageConfig, username string) error { // Modified to take username
|
||
configPath, err := getConfigPath(username)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
content, err := json.MarshalIndent(config, "", " ")
|
||
if err != nil {
|
||
return &AppError{
|
||
Type: ErrSystem,
|
||
Op: "generate JSON",
|
||
Path: "",
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// Create parent directories with proper permissions
|
||
dir := filepath.Dir(configPath)
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
return &AppError{
|
||
Type: ErrSystem,
|
||
Op: "create directory",
|
||
Path: dir,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// First ensure we can write to the file
|
||
if err := os.Chmod(configPath, 0666); err != nil && !os.IsNotExist(err) {
|
||
return &AppError{
|
||
Type: ErrSystem,
|
||
Op: "modify file permissions",
|
||
Path: configPath,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// Write to temporary file first
|
||
tmpPath := configPath + ".tmp"
|
||
if err := os.WriteFile(tmpPath, content, 0666); err != nil {
|
||
return &AppError{
|
||
Type: ErrSystem,
|
||
Op: "write temporary file",
|
||
Path: tmpPath,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// Ensure proper permissions on temporary file
|
||
if err := os.Chmod(tmpPath, 0444); err != nil {
|
||
os.Remove(tmpPath)
|
||
return &AppError{
|
||
Type: ErrSystem,
|
||
Op: "set temporary file permissions",
|
||
Path: tmpPath,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// Atomic rename
|
||
if err := os.Rename(tmpPath, configPath); err != nil {
|
||
os.Remove(tmpPath)
|
||
return &AppError{
|
||
Type: ErrSystem,
|
||
Op: "rename file",
|
||
Path: configPath,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
// Sync the directory to ensure changes are written to disk
|
||
if dir, err := os.Open(filepath.Dir(configPath)); err == nil {
|
||
dir.Sync()
|
||
dir.Close()
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func readExistingConfig(username string) (*StorageConfig, error) { // Modified to take username
|
||
configPath, err := getConfigPath(username)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
data, err := os.ReadFile(configPath)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return nil, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
var config StorageConfig
|
||
if err := json.Unmarshal(data, &config); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &config, nil
|
||
}
|
||
|
||
// Process Management / 进程管理
|
||
type ProcessManager struct {
|
||
config *SystemConfig
|
||
}
|
||
|
||
func (pm *ProcessManager) killCursorProcesses() error {
|
||
ctx, cancel := context.WithTimeout(context.Background(), pm.config.Timeout)
|
||
defer cancel()
|
||
|
||
for attempt := 0; attempt < pm.config.RetryAttempts; attempt++ {
|
||
if err := pm.killProcess(ctx); err != nil {
|
||
time.Sleep(pm.config.RetryDelay)
|
||
continue
|
||
}
|
||
return nil
|
||
}
|
||
|
||
return &AppError{
|
||
Type: ErrProcess,
|
||
Op: "kill_processes",
|
||
Err: errors.New("failed to kill all Cursor processes after retries"),
|
||
}
|
||
}
|
||
|
||
func (pm *ProcessManager) killProcess(ctx context.Context) error {
|
||
if runtime.GOOS == "windows" {
|
||
return pm.killWindowsProcess(ctx)
|
||
}
|
||
return pm.killUnixProcess(ctx)
|
||
}
|
||
|
||
func (pm *ProcessManager) killWindowsProcess(ctx context.Context) error {
|
||
exec.CommandContext(ctx, "taskkill", "/IM", "Cursor.exe").Run()
|
||
time.Sleep(pm.config.RetryDelay)
|
||
exec.CommandContext(ctx, "taskkill", "/F", "/IM", "Cursor.exe").Run()
|
||
return nil
|
||
}
|
||
|
||
func (pm *ProcessManager) killUnixProcess(ctx context.Context) error {
|
||
// Search for the process by it's executable name (AppRun) in ps output
|
||
cmd := exec.CommandContext(ctx, "ps", "aux")
|
||
output, err := cmd.Output()
|
||
if err != nil {
|
||
return fmt.Errorf("failed to execute ps command: %w", err)
|
||
}
|
||
|
||
lines := strings.Split(string(output), "\n")
|
||
for _, line := range lines {
|
||
if strings.Contains(line, "AppRun") {
|
||
parts := strings.Fields(line)
|
||
if len(parts) > 1 {
|
||
pid := parts[1]
|
||
if err := pm.forceKillProcess(ctx, pid); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
// handle lowercase
|
||
if strings.Contains(line, "apprun") {
|
||
parts := strings.Fields(line)
|
||
if len(parts) > 1 {
|
||
pid := parts[1]
|
||
if err := pm.forceKillProcess(ctx, pid); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// helper function to kill process by pid
|
||
func (pm *ProcessManager) forceKillProcess(ctx context.Context, pid string) error {
|
||
// First try graceful termination
|
||
if err := exec.CommandContext(ctx, "kill", pid).Run(); err == nil {
|
||
// Wait for processes to terminate gracefully
|
||
time.Sleep(2 * time.Second)
|
||
}
|
||
|
||
// Force kill if still running
|
||
if err := exec.CommandContext(ctx, "kill", "-9", pid).Run(); err != nil {
|
||
return fmt.Errorf("failed to force kill process %s: %w", pid, err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func checkCursorRunning() bool {
|
||
cmd := exec.Command("ps", "aux")
|
||
output, err := cmd.Output()
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
lines := strings.Split(string(output), "\n")
|
||
for _, line := range lines {
|
||
if strings.Contains(line, "AppRun") || strings.Contains(line, "apprun") {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// UI Components / UI组件
|
||
type UI struct {
|
||
config *UIConfig
|
||
spinner *ProgressSpinner
|
||
}
|
||
|
||
func NewUI(config *UIConfig) *UI {
|
||
return &UI{
|
||
config: config,
|
||
spinner: NewProgressSpinner(""),
|
||
}
|
||
}
|
||
|
||
func (ui *UI) showProgress(message string) {
|
||
ui.spinner.message = message
|
||
ui.spinner.Start()
|
||
defer ui.spinner.Stop()
|
||
|
||
ticker := time.NewTicker(ui.config.Spinner.Delay)
|
||
defer ticker.Stop()
|
||
|
||
for i := 0; i < 15; i++ {
|
||
<-ticker.C
|
||
ui.spinner.Spin()
|
||
}
|
||
}
|
||
|
||
// Display Functions / 显示函数
|
||
func showSuccess() {
|
||
text := texts[currentLanguage]
|
||
successColor := color.New(color.FgGreen, color.Bold)
|
||
warningColor := color.New(color.FgYellow, color.Bold)
|
||
pathColor := color.New(color.FgCyan)
|
||
|
||
// Clear any previous output
|
||
fmt.Println()
|
||
|
||
if currentLanguage == EN {
|
||
// English messages with extra spacing
|
||
successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||
successColor.Printf("%s\n", text.SuccessMessage)
|
||
fmt.Println()
|
||
warningColor.Printf("%s\n", text.RestartMessage)
|
||
successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||
} else {
|
||
// Chinese messages with extra spacing
|
||
successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||
successColor.Printf("%s\n", text.SuccessMessage)
|
||
fmt.Println()
|
||
warningColor.Printf("%s\n", text.RestartMessage)
|
||
successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━<E29481><E29481><EFBFBD>━━━")
|
||
}
|
||
|
||
// Add spacing before config location
|
||
fmt.Println()
|
||
|
||
username := os.Getenv("SUDO_USER")
|
||
if username == "" {
|
||
user, err := user.Current()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
username = user.Username
|
||
}
|
||
if configPath, err := getConfigPath(username); err == nil {
|
||
pathColor.Printf("%s\n%s\n", text.ConfigLocation, configPath)
|
||
}
|
||
}
|
||
|
||
func showPrivilegeError() {
|
||
text := texts[currentLanguage]
|
||
red := color.New(color.FgRed, color.Bold)
|
||
yellow := color.New(color.FgYellow)
|
||
|
||
if currentLanguage == EN {
|
||
red.Println(text.PrivilegeError)
|
||
if runtime.GOOS == "windows" {
|
||
yellow.Println(text.RunAsAdmin)
|
||
} else {
|
||
yellow.Printf("%s\n%s\n", text.RunWithSudo, fmt.Sprintf(text.SudoExample, os.Args[0]))
|
||
}
|
||
} else {
|
||
red.Printf("\n%s\n", text.PrivilegeError)
|
||
if runtime.GOOS == "windows" {
|
||
yellow.Printf("%s\n", text.RunAsAdmin)
|
||
} else {
|
||
yellow.Printf("%s\n%s\n", text.RunWithSudo, fmt.Sprintf(text.SudoExample, os.Args[0]))
|
||
}
|
||
}
|
||
}
|
||
|
||
// System Functions / 系统函数
|
||
func checkAdminPrivileges() (bool, error) {
|
||
switch runtime.GOOS {
|
||
case "windows":
|
||
cmd := exec.Command("whoami", "/groups")
|
||
output, err := cmd.Output()
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return strings.Contains(string(output), "S-1-16-12288") ||
|
||
strings.Contains(string(output), "S-1-5-32-544"), nil
|
||
|
||
case "darwin", "linux":
|
||
currentUser, err := user.Current()
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to get current user: %v", err)
|
||
}
|
||
return currentUser.Uid == "0", nil
|
||
|
||
default:
|
||
return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||
}
|
||
}
|
||
|
||
func detectLanguage() Language {
|
||
// Check common environment variables
|
||
for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} {
|
||
if lang := os.Getenv(envVar); lang != "" {
|
||
if strings.Contains(strings.ToLower(lang), "zh") {
|
||
return CN
|
||
}
|
||
}
|
||
}
|
||
|
||
// Windows-specific language check
|
||
if runtime.GOOS == "windows" {
|
||
cmd := exec.Command("powershell", "-Command",
|
||
"[System.Globalization.CultureInfo]::CurrentUICulture.Name")
|
||
output, err := cmd.Output()
|
||
if err == nil {
|
||
lang := strings.ToLower(strings.TrimSpace(string(output)))
|
||
if strings.HasPrefix(lang, "zh") {
|
||
return CN
|
||
}
|
||
}
|
||
|
||
// Check Windows locale
|
||
cmd = exec.Command("wmic", "os", "get", "locale")
|
||
output, err = cmd.Output()
|
||
if err == nil && strings.Contains(string(output), "2052") {
|
||
return CN
|
||
}
|
||
}
|
||
|
||
// Check Unix locale
|
||
if runtime.GOOS != "windows" {
|
||
cmd := exec.Command("locale")
|
||
output, err := cmd.Output()
|
||
if err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") {
|
||
return CN
|
||
}
|
||
}
|
||
|
||
return EN
|
||
}
|
||
|
||
func selfElevate() error {
|
||
switch runtime.GOOS {
|
||
case "windows":
|
||
// Set automated mode for the elevated process
|
||
os.Setenv("AUTOMATED_MODE", "1")
|
||
|
||
verb := "runas"
|
||
exe, _ := os.Executable()
|
||
cwd, _ := os.Getwd()
|
||
args := strings.Join(os.Args[1:], " ")
|
||
|
||
cmd := exec.Command("cmd", "/C", "start", verb, exe, args)
|
||
cmd.Dir = cwd
|
||
return cmd.Run()
|
||
|
||
case "darwin", "linux":
|
||
// Set automated mode for the elevated process
|
||
os.Setenv("AUTOMATED_MODE", "1")
|
||
|
||
exe, err := os.Executable()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
cmd := exec.Command("sudo", append([]string{exe}, os.Args[1:]...)...)
|
||
cmd.Stdin = os.Stdin
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Stderr = os.Stderr
|
||
return cmd.Run()
|
||
|
||
default:
|
||
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||
}
|
||
}
|
||
|
||
// Utility Functions / 实用函数
|
||
func handleError(err error) {
|
||
if err == nil {
|
||
return
|
||
}
|
||
|
||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||
|
||
switch e := err.(type) {
|
||
case *AppError:
|
||
logger.Printf("[ERROR] %v\n", e)
|
||
if e.Type == ErrPermission {
|
||
showPrivilegeError()
|
||
}
|
||
default:
|
||
logger.Printf("[ERROR] Unexpected error: %v\n", err)
|
||
}
|
||
}
|
||
|
||
func waitExit() {
|
||
// Skip waiting in automated mode
|
||
if os.Getenv("AUTOMATED_MODE") == "1" {
|
||
return
|
||
}
|
||
|
||
if currentLanguage == EN {
|
||
fmt.Println("\nPress Enter to exit...")
|
||
} else {
|
||
fmt.Println("\n按回车键退出程序...")
|
||
}
|
||
os.Stdout.Sync()
|
||
bufio.NewReader(os.Stdin).ReadString('\n')
|
||
}
|
||
|
||
// Add this new function near the other process management functions
|
||
func ensureCursorClosed() error {
|
||
maxAttempts := 3
|
||
text := texts[currentLanguage]
|
||
|
||
showProcessStatus(text.CheckingProcesses)
|
||
|
||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||
if !checkCursorRunning() {
|
||
showProcessStatus(text.ProcessesClosed)
|
||
fmt.Println() // New line after status
|
||
return nil
|
||
}
|
||
|
||
if currentLanguage == EN {
|
||
showProcessStatus(fmt.Sprintf("Please close Cursor before continuing. Attempt %d/%d\n%s",
|
||
attempt, maxAttempts, text.PleaseWait))
|
||
} else {
|
||
showProcessStatus(fmt.Sprintf("请在继续之前关闭 Cursor。尝试 %d/%d\n%s",
|
||
attempt, maxAttempts, text.PleaseWait))
|
||
}
|
||
|
||
time.Sleep(5 * time.Second)
|
||
}
|
||
|
||
return errors.New("cursor is still running")
|
||
}
|
||
|
||
func main() {
|
||
// Initialize error recovery
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
log.Printf("Panic recovered: %v\n", r)
|
||
debug.PrintStack()
|
||
waitExit()
|
||
}
|
||
}()
|
||
|
||
var username string
|
||
if username = os.Getenv("SUDO_USER"); username == "" {
|
||
user, err := user.Current()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
username = user.Username
|
||
}
|
||
log.Println("Current user: ", username)
|
||
|
||
// Initialize configuration
|
||
ui := NewUI(&UIConfig{
|
||
Language: detectLanguage(),
|
||
Theme: "default",
|
||
Spinner: SpinnerConfig{
|
||
Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||
Delay: 100 * time.Millisecond,
|
||
},
|
||
})
|
||
|
||
// Check privileges
|
||
os.Stdout.Sync()
|
||
currentLanguage = detectLanguage()
|
||
log.Println("Current language: ", currentLanguage)
|
||
isAdmin, err := checkAdminPrivileges()
|
||
if err != nil {
|
||
handleError(err)
|
||
waitExit()
|
||
return
|
||
}
|
||
|
||
if !isAdmin && runtime.GOOS == "windows" {
|
||
if currentLanguage == EN {
|
||
fmt.Println("\nRequesting administrator privileges...")
|
||
} else {
|
||
fmt.Println("\n请求管理员权限...")
|
||
}
|
||
if err := selfElevate(); err != nil {
|
||
handleError(err)
|
||
showPrivilegeError()
|
||
waitExit()
|
||
return
|
||
}
|
||
return
|
||
} else if !isAdmin {
|
||
showPrivilegeError()
|
||
waitExit()
|
||
return
|
||
}
|
||
|
||
// Ensure all Cursor instances are closed
|
||
if err := ensureCursorClosed(); err != nil {
|
||
if currentLanguage == EN {
|
||
fmt.Println("\nError: Please close Cursor manually before running this program.")
|
||
} else {
|
||
fmt.Println("\n错误:请在运行此程序之前手动关闭 Cursor。")
|
||
}
|
||
waitExit()
|
||
return
|
||
}
|
||
|
||
// Process management
|
||
pm := &ProcessManager{
|
||
config: &SystemConfig{
|
||
RetryAttempts: 3,
|
||
RetryDelay: time.Second,
|
||
Timeout: 30 * time.Second,
|
||
},
|
||
}
|
||
if checkCursorRunning() {
|
||
text := texts[currentLanguage]
|
||
showProcessStatus(text.ClosingProcesses)
|
||
|
||
if err := pm.killCursorProcesses(); err != nil {
|
||
fmt.Println() // New line after status
|
||
if currentLanguage == EN {
|
||
fmt.Println("Warning: Could not close all Cursor instances. Please close them manually.")
|
||
} else {
|
||
fmt.Println("警告:无法关闭所有 Cursor 实例,请手动关闭。")
|
||
}
|
||
waitExit()
|
||
return
|
||
}
|
||
|
||
time.Sleep(2 * time.Second)
|
||
if checkCursorRunning() {
|
||
fmt.Println() // New line after status
|
||
if currentLanguage == EN {
|
||
fmt.Println("\nWarning: Cursor is still running. Please close it manually.")
|
||
} else {
|
||
fmt.Println("\n警告:Cursor 仍在运行,请手动关闭。")
|
||
}
|
||
waitExit()
|
||
return
|
||
}
|
||
|
||
showProcessStatus(text.ProcessesClosed)
|
||
fmt.Println() // New line after status
|
||
}
|
||
|
||
// Clear screen and show banner
|
||
clearScreen()
|
||
printCyberpunkBanner()
|
||
|
||
// Read and update configuration
|
||
oldConfig, err := readExistingConfig(username) // add username parameter
|
||
if err != nil {
|
||
oldConfig = nil
|
||
}
|
||
|
||
storageConfig, err := loadAndUpdateConfig(ui, username) // add username parameter
|
||
if err != nil {
|
||
handleError(err)
|
||
waitExit()
|
||
return
|
||
}
|
||
|
||
// Show changes and save
|
||
showIdComparison(oldConfig, storageConfig)
|
||
|
||
if err := saveConfig(storageConfig, username); err != nil { // add username parameter
|
||
handleError(err)
|
||
waitExit()
|
||
return
|
||
}
|
||
|
||
// Show success and exit
|
||
showSuccess()
|
||
if currentLanguage == EN {
|
||
fmt.Println("\nOperation completed!")
|
||
} else {
|
||
fmt.Println("\n操作完成!")
|
||
}
|
||
|
||
// Check if running in automated mode
|
||
if os.Getenv("AUTOMATED_MODE") == "1" {
|
||
return
|
||
}
|
||
|
||
waitExit()
|
||
}
|
||
|
||
// Progress spinner functions / 进度条函数
|
||
func NewProgressSpinner(message string) *ProgressSpinner {
|
||
return &ProgressSpinner{
|
||
frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||
message: message,
|
||
}
|
||
}
|
||
|
||
func (s *ProgressSpinner) Spin() {
|
||
frame := s.frames[s.current%len(s.frames)]
|
||
s.current++
|
||
fmt.Printf("\r%s %s", color.CyanString(frame), s.message)
|
||
}
|
||
|
||
func (s *ProgressSpinner) Stop() {
|
||
fmt.Println()
|
||
}
|
||
|
||
func (s *ProgressSpinner) Start() {
|
||
s.current = 0
|
||
}
|
||
|
||
// Display utility functions / 显示工具函数
|
||
func clearScreen() {
|
||
var cmd *exec.Cmd
|
||
if runtime.GOOS == "windows" {
|
||
cmd = exec.Command("cmd", "/c", "cls")
|
||
} else {
|
||
cmd = exec.Command("clear")
|
||
}
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Run()
|
||
}
|
||
|
||
func printCyberpunkBanner() {
|
||
cyan := color.New(color.FgCyan, color.Bold)
|
||
yellow := color.New(color.FgYellow, color.Bold)
|
||
magenta := color.New(color.FgMagenta, color.Bold)
|
||
green := color.New(color.FgGreen, color.Bold)
|
||
|
||
banner := `
|
||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
|
||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
|
||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
|
||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
|
||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
|
||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
|
||
`
|
||
cyan.Println(banner)
|
||
yellow.Println("\t\t>> Cursor ID Modifier v1.0 <<")
|
||
magenta.Println("\t\t [ By Pancake Fruit Rolled Shark Chili ]")
|
||
|
||
langText := "当前语言/Language: "
|
||
if currentLanguage == CN {
|
||
langText += "简体中文"
|
||
} else {
|
||
langText += "English"
|
||
}
|
||
green.Printf("\n\t\t %s\n\n", langText)
|
||
}
|
||
|
||
func showIdComparison(oldConfig *StorageConfig, newConfig *StorageConfig) {
|
||
cyan := color.New(color.FgCyan)
|
||
yellow := color.New(color.FgYellow)
|
||
|
||
fmt.Println("\n=== ID Modification Comparison / ID 修改对比 ===")
|
||
|
||
if oldConfig != nil {
|
||
cyan.Println("\n[Original IDs / 原始 ID]")
|
||
yellow.Printf("Machine ID: %s\n", oldConfig.TelemetryMachineId)
|
||
yellow.Printf("Mac Machine ID: %s\n", oldConfig.TelemetryMacMachineId)
|
||
yellow.Printf("Dev Device ID: %s\n", oldConfig.TelemetryDevDeviceId)
|
||
}
|
||
|
||
cyan.Println("\n[Newly Generated IDs / 新生成 ID]")
|
||
yellow.Printf("Machine ID: %s\n", newConfig.TelemetryMachineId)
|
||
yellow.Printf("Mac Machine ID: %s\n", newConfig.TelemetryMacMachineId)
|
||
yellow.Printf("Dev Device ID: %s\n", newConfig.TelemetryDevDeviceId)
|
||
yellow.Printf("SQM ID: %s\n", newConfig.TelemetrySqmId)
|
||
fmt.Println()
|
||
}
|
||
|
||
// Configuration functions / 配置函数
|
||
func loadAndUpdateConfig(ui *UI, username string) (*StorageConfig, error) { // add username parameter
|
||
configPath, err := getConfigPath(username) // add username parameter
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
text := texts[currentLanguage]
|
||
ui.showProgress(text.ReadingConfig)
|
||
|
||
oldConfig, err := readExistingConfig(username) // add username parameter
|
||
if err != nil && !os.IsNotExist(err) {
|
||
return nil, &AppError{
|
||
Type: ErrSystem,
|
||
Op: "read config file",
|
||
Path: configPath,
|
||
Err: err,
|
||
}
|
||
}
|
||
|
||
ui.showProgress(text.GeneratingIds)
|
||
return NewStorageConfig(oldConfig), nil
|
||
}
|
||
|
||
// Add a new function to show process status
|
||
func showProcessStatus(message string) {
|
||
cyan := color.New(color.FgCyan)
|
||
fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line
|
||
fmt.Printf("\r%s", cyan.Sprint("⚡ "+message))
|
||
}
|