mirror of
https://github.com/anotherhadi/settuings.git
synced 2026-10-05 08:18:23 +02:00
112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
// Package power reads and controls the active power profile (via
|
|
// powerprofilesctl / power-profiles-daemon) and reports battery/AC status.
|
|
// Profile control shells out to a CLI tool, in the same spirit as the
|
|
// bluetooth and audio packages; battery status has no universally-installed
|
|
// CLI equivalent, so it's read directly from /sys/class/power_supply.
|
|
package power
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var ErrNotFound = errors.New("powerprofilesctl not found in PATH")
|
|
|
|
const defaultTimeout = 15 * time.Second
|
|
|
|
// Profile is one entry from `powerprofilesctl list`.
|
|
type Profile struct {
|
|
Name string
|
|
Active bool
|
|
}
|
|
|
|
// CheckAvailable reports whether powerprofilesctl is installed and
|
|
// power-profiles-daemon is reachable. Call it before using the rest of
|
|
// this file's functions.
|
|
func CheckAvailable() error {
|
|
if _, err := exec.LookPath("powerprofilesctl"); err != nil {
|
|
return ErrNotFound
|
|
}
|
|
_, err := run("get")
|
|
return err
|
|
}
|
|
|
|
// Installed reports whether powerprofilesctl is present in PATH, without
|
|
// probing power-profiles-daemon. Cheap enough to call when deciding whether
|
|
// to show the Power page in the sidebar.
|
|
func Installed() bool {
|
|
_, err := exec.LookPath("powerprofilesctl")
|
|
return err == nil
|
|
}
|
|
|
|
// ListProfiles returns the available profiles in the order reported by
|
|
// powerprofilesctl, each flagged with whether it's currently active.
|
|
func ListProfiles() ([]Profile, error) {
|
|
out, err := run("list")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var profiles []Profile
|
|
for _, line := range strings.Split(out, "\n") {
|
|
// Top-level profile entries are indented two spaces (or "* " when
|
|
// active); their attribute lines (Driver:, Degraded:, ...) are
|
|
// indented four, so those are skipped here.
|
|
if line == "" || strings.HasPrefix(line, " ") {
|
|
continue
|
|
}
|
|
|
|
active := false
|
|
trimmed := line
|
|
switch {
|
|
case strings.HasPrefix(trimmed, "* "):
|
|
active = true
|
|
trimmed = trimmed[2:]
|
|
case strings.HasPrefix(trimmed, " "):
|
|
trimmed = trimmed[2:]
|
|
default:
|
|
continue
|
|
}
|
|
|
|
name := strings.TrimSuffix(trimmed, ":")
|
|
if name == "" {
|
|
continue
|
|
}
|
|
profiles = append(profiles, Profile{Name: name, Active: active})
|
|
}
|
|
return profiles, nil
|
|
}
|
|
|
|
// SetActive switches the active power profile.
|
|
func SetActive(name string) error {
|
|
_, err := run("set", name)
|
|
return err
|
|
}
|
|
|
|
func run(args ...string) (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(ctx, "powerprofilesctl", args...) // #nosec G204 -- args are fixed powerprofilesctl subcommands or profile names read back from `powerprofilesctl list`, never shell-interpreted
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
if errors.Is(err, exec.ErrNotFound) {
|
|
return "", ErrNotFound
|
|
}
|
|
msg := strings.TrimSpace(stderr.String())
|
|
if msg == "" {
|
|
msg = err.Error()
|
|
}
|
|
return "", fmt.Errorf("powerprofilesctl %s: %s", strings.Join(args, " "), msg)
|
|
}
|
|
return stdout.String(), nil
|
|
}
|