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

- Move panic recovery defer to the beginning of main function to ensure it can catch panics from all subsequent function calls - Remove unused setupErrorRecovery function - Add explanatory comment for the defer placement This change ensures proper error handling throughout the entire program execution, rather than just within the setupErrorRecovery function scope.
329 lines
7.7 KiB
Go
329 lines
7.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"strings"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/yuaotian/go-cursor-help/internal/config"
|
|
"github.com/yuaotian/go-cursor-help/internal/lang"
|
|
"github.com/yuaotian/go-cursor-help/internal/process"
|
|
"github.com/yuaotian/go-cursor-help/internal/ui"
|
|
"github.com/yuaotian/go-cursor-help/pkg/idgen"
|
|
)
|
|
|
|
// Global variables
|
|
var (
|
|
version = "dev"
|
|
setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode")
|
|
showVersion = flag.Bool("v", false, "show version information")
|
|
log = logrus.New()
|
|
)
|
|
|
|
func main() {
|
|
// Place defer at the beginning of main to ensure it can catch panics from all subsequent function calls
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Errorf("Panic recovered: %v\n", r)
|
|
debug.PrintStack()
|
|
waitExit()
|
|
}
|
|
}()
|
|
|
|
handleFlags()
|
|
setupLogger()
|
|
|
|
username := getCurrentUser()
|
|
log.Debug("Running as user:", username)
|
|
|
|
// Initialize components
|
|
display := ui.NewDisplay(nil)
|
|
configManager := initConfigManager(username)
|
|
generator := idgen.NewGenerator()
|
|
processManager := process.NewManager(nil, log)
|
|
|
|
// Check and handle privileges
|
|
if err := handlePrivileges(display); err != nil {
|
|
return
|
|
}
|
|
|
|
// Setup display
|
|
setupDisplay(display)
|
|
|
|
text := lang.GetText()
|
|
|
|
// Handle Cursor processes
|
|
if err := handleCursorProcesses(display, processManager); err != nil {
|
|
return
|
|
}
|
|
|
|
// Handle configuration
|
|
oldConfig := readExistingConfig(display, configManager, text)
|
|
newConfig := generateNewConfig(display, generator, oldConfig, text)
|
|
|
|
if err := saveConfiguration(display, configManager, newConfig); err != nil {
|
|
return
|
|
}
|
|
|
|
// Show completion messages
|
|
showCompletionMessages(display)
|
|
|
|
if os.Getenv("AUTOMATED_MODE") != "1" {
|
|
waitExit()
|
|
}
|
|
}
|
|
|
|
func handleFlags() {
|
|
flag.Parse()
|
|
if *showVersion {
|
|
fmt.Printf("Cursor ID Modifier v%s\n", version)
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func setupLogger() {
|
|
log.SetFormatter(&logrus.TextFormatter{
|
|
FullTimestamp: true,
|
|
DisableLevelTruncation: true,
|
|
PadLevelText: true,
|
|
})
|
|
log.SetLevel(logrus.InfoLevel)
|
|
}
|
|
|
|
func getCurrentUser() string {
|
|
if username := os.Getenv("SUDO_USER"); username != "" {
|
|
return username
|
|
}
|
|
|
|
user, err := user.Current()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return user.Username
|
|
}
|
|
|
|
func initConfigManager(username string) *config.Manager {
|
|
configManager, err := config.NewManager(username)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return configManager
|
|
}
|
|
|
|
func handlePrivileges(display *ui.Display) error {
|
|
isAdmin, err := checkAdminPrivileges()
|
|
if err != nil {
|
|
log.Error(err)
|
|
waitExit()
|
|
return err
|
|
}
|
|
|
|
if !isAdmin {
|
|
if runtime.GOOS == "windows" {
|
|
return handleWindowsPrivileges(display)
|
|
}
|
|
display.ShowPrivilegeError(
|
|
lang.GetText().PrivilegeError,
|
|
lang.GetText().RunWithSudo,
|
|
lang.GetText().SudoExample,
|
|
)
|
|
waitExit()
|
|
return fmt.Errorf("insufficient privileges")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleWindowsPrivileges(display *ui.Display) error {
|
|
message := "\nRequesting administrator privileges..."
|
|
if lang.GetCurrentLanguage() == lang.CN {
|
|
message = "\n请求管理员权限..."
|
|
}
|
|
fmt.Println(message)
|
|
|
|
if err := selfElevate(); err != nil {
|
|
log.Error(err)
|
|
display.ShowPrivilegeError(
|
|
lang.GetText().PrivilegeError,
|
|
lang.GetText().RunAsAdmin,
|
|
lang.GetText().RunWithSudo,
|
|
lang.GetText().SudoExample,
|
|
)
|
|
waitExit()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setupDisplay(display *ui.Display) {
|
|
if err := display.ClearScreen(); err != nil {
|
|
log.Warn("Failed to clear screen:", err)
|
|
}
|
|
display.ShowLogo()
|
|
fmt.Println()
|
|
}
|
|
|
|
func handleCursorProcesses(display *ui.Display, processManager *process.Manager) error {
|
|
if os.Getenv("AUTOMATED_MODE") == "1" {
|
|
log.Debug("Running in automated mode, skipping Cursor process closing")
|
|
return nil
|
|
}
|
|
|
|
display.ShowProgress("Closing Cursor...")
|
|
log.Debug("Attempting to close Cursor processes")
|
|
|
|
if err := processManager.KillCursorProcesses(); err != nil {
|
|
log.Error("Failed to close Cursor:", err)
|
|
display.StopProgress()
|
|
display.ShowError("Failed to close Cursor. Please close it manually and try again.")
|
|
waitExit()
|
|
return err
|
|
}
|
|
|
|
if processManager.IsCursorRunning() {
|
|
log.Error("Cursor processes still detected after closing")
|
|
display.StopProgress()
|
|
display.ShowError("Failed to close Cursor completely. Please close it manually and try again.")
|
|
waitExit()
|
|
return fmt.Errorf("cursor still running")
|
|
}
|
|
|
|
log.Debug("Successfully closed all Cursor processes")
|
|
display.StopProgress()
|
|
fmt.Println()
|
|
return nil
|
|
}
|
|
|
|
func readExistingConfig(display *ui.Display, configManager *config.Manager, text lang.TextResource) *config.StorageConfig {
|
|
fmt.Println()
|
|
display.ShowProgress(text.ReadingConfig)
|
|
oldConfig, err := configManager.ReadConfig()
|
|
if err != nil {
|
|
log.Warn("Failed to read existing config:", err)
|
|
oldConfig = nil
|
|
}
|
|
display.StopProgress()
|
|
fmt.Println()
|
|
return oldConfig
|
|
}
|
|
|
|
func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfig *config.StorageConfig, text lang.TextResource) *config.StorageConfig {
|
|
display.ShowProgress(text.GeneratingIds)
|
|
newConfig := &config.StorageConfig{}
|
|
|
|
if machineID, err := generator.GenerateMachineID(); err != nil {
|
|
log.Fatal("Failed to generate machine ID:", err)
|
|
} else {
|
|
newConfig.TelemetryMachineId = machineID
|
|
}
|
|
|
|
if macMachineID, err := generator.GenerateMacMachineID(); err != nil {
|
|
log.Fatal("Failed to generate MAC machine ID:", err)
|
|
} else {
|
|
newConfig.TelemetryMacMachineId = macMachineID
|
|
}
|
|
|
|
if deviceID, err := generator.GenerateDeviceID(); err != nil {
|
|
log.Fatal("Failed to generate device ID:", err)
|
|
} else {
|
|
newConfig.TelemetryDevDeviceId = deviceID
|
|
}
|
|
|
|
if oldConfig != nil && oldConfig.TelemetrySqmId != "" {
|
|
newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId
|
|
} else if sqmID, err := generator.GenerateSQMID(); err != nil {
|
|
log.Fatal("Failed to generate SQM ID:", err)
|
|
} else {
|
|
newConfig.TelemetrySqmId = sqmID
|
|
}
|
|
|
|
display.StopProgress()
|
|
fmt.Println()
|
|
return newConfig
|
|
}
|
|
|
|
func saveConfiguration(display *ui.Display, configManager *config.Manager, newConfig *config.StorageConfig) error {
|
|
display.ShowProgress("Saving configuration...")
|
|
if err := configManager.SaveConfig(newConfig, *setReadOnly); err != nil {
|
|
log.Error(err)
|
|
waitExit()
|
|
return err
|
|
}
|
|
display.StopProgress()
|
|
fmt.Println()
|
|
return nil
|
|
}
|
|
|
|
func showCompletionMessages(display *ui.Display) {
|
|
display.ShowSuccess(lang.GetText().SuccessMessage, lang.GetText().RestartMessage)
|
|
fmt.Println()
|
|
|
|
message := "Operation completed!"
|
|
if lang.GetCurrentLanguage() == lang.CN {
|
|
message = "操作完成!"
|
|
}
|
|
display.ShowInfo(message)
|
|
}
|
|
|
|
func waitExit() {
|
|
fmt.Print(lang.GetText().PressEnterToExit)
|
|
os.Stdout.Sync()
|
|
bufio.NewReader(os.Stdin).ReadString('\n')
|
|
}
|
|
|
|
func checkAdminPrivileges() (bool, error) {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
cmd := exec.Command("net", "session")
|
|
return cmd.Run() == nil, nil
|
|
|
|
case "darwin", "linux":
|
|
currentUser, err := user.Current()
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get current user: %w", err)
|
|
}
|
|
return currentUser.Uid == "0", nil
|
|
|
|
default:
|
|
return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
|
}
|
|
}
|
|
|
|
func selfElevate() error {
|
|
os.Setenv("AUTOMATED_MODE", "1")
|
|
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
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":
|
|
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)
|
|
}
|
|
}
|