src

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

power.go (3052B)


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