package main import ( "fmt" "os" "os/exec" "strings" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" ) // getDisplayServerType returns the type of display server being used func getDisplayServerType() string { return "X11" } // getDesktopEntry returns the appropriate desktop entry path func getDesktopEntry() string { return "/usr/share/applications/system76-power-gui-x11.desktop" } // executeCommand runs a given command with sudo and returns its output or an error. func executeCommand(command string, args ...string) (string, error) { cmdArgs := append([]string{command}, args...) cmd := exec.Command("pkexec", cmdArgs...) // Set up command environment cmd.Env = append(os.Environ(), "DISPLAY="+os.Getenv("DISPLAY"), ) output, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("command execution failed: %v\nOutput: %s", err, string(output)) } return string(output), nil } // getStatus retrieves the current system76-power profile. func getStatus() (string, error) { // First check if system76-power is available if _, err := exec.LookPath("system76-power"); err != nil { return "", fmt.Errorf("system76-power is not installed or not in PATH: %v", err) } // Try to get the profile output, err := executeCommand("system76-power", "profile") if err != nil { return "", fmt.Errorf("failed to get current profile: %v", err) } profile := strings.TrimSpace(output) if profile == "" { return "", fmt.Errorf("received empty profile from system76-power") } return profile, nil } // isNonCriticalError checks if the error is non-critical (like SCSI errors) func isNonCriticalError(output string) bool { // Check for SCSI-related errors in DBus error output return strings.Contains(output, "failed to set scsi host profiles") && strings.Contains(output, "org.freedesktop.DBus.Error.Failed") } // trySetProfile attempts to set the profile once func trySetProfile(profile string) error { output, err := executeCommand("system76-power", "profile", profile) if err != nil { // Check if this is a non-critical error if isNonCriticalError(output) { // Wait a moment for the profile to be applied time.Sleep(time.Millisecond * 500) // Verify if the profile was set despite the SCSI error currentProfile, checkErr := getStatus() if checkErr == nil && currentProfile == profile { fmt.Printf("Profile set to %s (with non-critical SCSI warnings)\n", profile) return nil } } return fmt.Errorf("failed to set profile: %v", err) } return nil } // setProfile sets the system76-power profile to the specified mode with one retry. func setProfile(profile string) error { // First attempt err := trySetProfile(profile) if err == nil { fmt.Printf("Profile set to %s successfully.\n", profile) return nil } // If first attempt failed, wait a bit and retry once fmt.Printf("First attempt failed, retrying after delay...\n") time.Sleep(time.Second * 1) // Second attempt err = trySetProfile(profile) if err == nil { fmt.Printf("Profile set to %s successfully on second attempt.\n", profile) return nil } return fmt.Errorf("failed to set profile to %s after retry: %v", profile, err) } type powerApp struct { app fyne.App window fyne.Window } // createTrayMenu creates the menu for the window func (p *powerApp) createMenu() *fyne.MainMenu { menu := fyne.NewMainMenu( fyne.NewMenu("Power Profile", fyne.NewMenuItem("Battery Mode", func() { if err := setProfile("battery"); err != nil { showError(err, p.window) } }), fyne.NewMenuItem("Balanced Mode", func() { if err := setProfile("balanced"); err != nil { showError(err, p.window) } }), fyne.NewMenuItem("Performance Mode", func() { if err := setProfile("performance"); err != nil { showError(err, p.window) } }), fyne.NewMenuItemSeparator(), fyne.NewMenuItem("Quit", func() { p.app.Quit() }), ), ) return menu } // showError displays an error in a dialog with selectable text func showError(err error, window fyne.Window) { errEntry := widget.NewMultiLineEntry() errEntry.SetText(err.Error()) errEntry.Disable() d := dialog.NewCustom("Error", "OK", errEntry, window) d.Resize(fyne.NewSize(400, 100)) d.Show() } // updateStatus periodically updates the status label func (p *powerApp) updateStatus(statusLabel *widget.Label) { for { status, err := getStatus() if err != nil { fmt.Fprintf(os.Stderr, "Error updating status: %v\n", err) time.Sleep(5 * time.Second) continue } statusLabel.SetText(fmt.Sprintf("%s", status)) time.Sleep(1 * time.Second) } } func main() { // Create application a := app.New() w := a.NewWindow("System76 Power Profile") // Create our app structure powerApp := &powerApp{ app: a, window: w, } // Set up menu w.SetMainMenu(powerApp.createMenu()) // Create main window content statusLabel := widget.NewLabel("Current profile: Unknown") // Start background status updater go powerApp.updateStatus(statusLabel) // Create profile buttons batteryButton := widget.NewButton("Battery Mode", func() { if err := setProfile("battery"); err != nil { fmt.Fprintf(os.Stderr, "Error setting profile: %v\n", err) return } }) balancedButton := widget.NewButton("Balanced Mode", func() { if err := setProfile("balanced"); err != nil { fmt.Fprintf(os.Stderr, "Error setting profile: %v\n", err) return } }) performanceButton := widget.NewButton("Performance Mode", func() { if err := setProfile("performance"); err != nil { fmt.Fprintf(os.Stderr, "Error setting profile: %v\n", err) return } }) // Set up window content w.SetContent(container.NewVBox( statusLabel, widget.NewSeparator(), batteryButton, balancedButton, performanceButton, )) // Set window properties w.Resize(fyne.NewSize(300, 200)) w.SetCloseIntercept(func() { w.Hide() }) // Hide instead of close // Start application w.Show() a.Run() }