231 lines
5.9 KiB
Go
231 lines
5.9 KiB
Go
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()
|
|
}
|