You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

328 lines
7.7 KiB

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)
}
}