src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

power.go (3045B)


      1 package power
      2 
      3 import (
      4 	"bufio"
      5 	"context"
      6 	"fmt"
      7 	"math"
      8 	"os"
      9 	"slices"
     10 	"strconv"
     11 	"strings"
     12 )
     13 
     14 type Config struct {
     15 	Path string `json:"path"`
     16 }
     17 
     18 // https://www.kernel.org/doc/html/latest/power/power_supply_class.html
     19 const Path = "/sys/class/power_supply"
     20 
     21 type Block struct {
     22 	path string
     23 }
     24 
     25 func New(path string) *Block {
     26 	return &Block{path: path}
     27 }
     28 
     29 func (b *Block) Name() string {
     30 	return "power"
     31 }
     32 
     33 func (b *Block) Render(ctx context.Context) (string, error) {
     34 	// Get the power supplies.
     35 	var supplies []string
     36 
     37 	files, err := os.ReadDir(b.path)
     38 	if err != nil {
     39 		return "", fmt.Errorf("failed to read dir %s: %v", b.path, err)
     40 	}
     41 	for _, f := range files {
     42 		name := f.Name()
     43 
     44 		if name == "AC" || strings.Contains(name, "BAT") {
     45 			supplies = append(supplies, f.Name())
     46 		}
     47 	}
     48 	slices.Sort(supplies)
     49 
     50 	var sections = []string{}
     51 	for _, s := range supplies {
     52 		path := fmt.Sprintf("%s/%s/uevent", b.path, s)
     53 		f, err := os.Open(path)
     54 		if err != nil {
     55 			return "", fmt.Errorf(
     56 				"failed to open %s: %v", path, err,
     57 			)
     58 		}
     59 		defer f.Close()
     60 
     61 		switch {
     62 		case s == "AC":
     63 			sections = append(sections, outputAC(f))
     64 		case strings.Contains(s, "BAT"):
     65 			sections = append(sections, outputBAT(f))
     66 		}
     67 	}
     68 
     69 	var output strings.Builder
     70 	for i, s := range sections {
     71 		if i > 0 {
     72 			output.WriteRune(' ')
     73 			output.WriteRune(' ')
     74 		}
     75 		output.WriteString(s)
     76 	}
     77 
     78 	return output.String(), nil
     79 }
     80 
     81 func outputAC(f *os.File) string {
     82 	var scanner = bufio.NewScanner(f)
     83 	for scanner.Scan() {
     84 		k, v, found := strings.Cut(scanner.Text(), "=")
     85 		if !found {
     86 			continue
     87 		}
     88 		if k == "POWER_SUPPLY_ONLINE" {
     89 			online, err := strconv.ParseBool(v)
     90 			if err != nil {
     91 				break
     92 			}
     93 			if online {
     94 				return ""
     95 			} else {
     96 				return ""
     97 			}
     98 		}
     99 	}
    100 
    101 	return ""
    102 }
    103 
    104 func outputBAT(f *os.File) string {
    105 	// Compile the stats.
    106 	var (
    107 		stats   = map[string]string{}
    108 		scanner = bufio.NewScanner(f)
    109 	)
    110 	for scanner.Scan() {
    111 		k, v, found := strings.Cut(scanner.Text(), "=")
    112 		if !found {
    113 			continue
    114 		}
    115 		// Exit early if the battery is not present.
    116 		if k == "POWER_SUPPLY_PRESENT" && v == "0" {
    117 			return ""
    118 		}
    119 
    120 		stats[k] = v
    121 	}
    122 
    123 	// Assemble output.
    124 	var icon rune
    125 	switch stats["POWER_SUPPLY_STATUS"] {
    126 	case "Charging":
    127 		icon = ''
    128 	case "Discharging":
    129 		icon = ''
    130 	case "Full":
    131 		icon = ''
    132 	default:
    133 		icon = ''
    134 	}
    135 
    136 	// Get capacity.
    137 	var capacity string
    138 	if s, exists := stats["POWER_SUPPLY_CAPACITY"]; exists {
    139 		capacity = s + "%"
    140 	}
    141 
    142 	// Get remaining.
    143 	pn, _ := strconv.ParseFloat(stats["POWER_SUPPLY_POWER_NOW"], 64)
    144 	en, _ := strconv.ParseFloat(stats["POWER_SUPPLY_ENERGY_NOW"], 64)
    145 	if r := remaining(en, pn); r != "" {
    146 		return fmt.Sprintf("%c %s %s", icon, capacity, r)
    147 	}
    148 
    149 	return fmt.Sprintf("%c %s", icon, capacity)
    150 }
    151 
    152 func remaining(energy, power float64) string {
    153 	if energy == 0 || power == 0 {
    154 		return ""
    155 	}
    156 
    157 	// Calculate the remaining hours.
    158 	var hours = energy / power
    159 	if hours == 0 {
    160 		return ""
    161 	}
    162 
    163 	return fmt.Sprintf(
    164 		"%d:%02d",
    165 		int(hours),
    166 		int((hours-math.Floor(hours))*60),
    167 	)
    168 }