src

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

commit 740ed3793aca68b7078422edf4e3b3a3d337f50c
parent 0843da2f58feeff6fd5e8121794a31ed8cd66de2
Author: dwrz <dwrz@dwrz.net>
Date:   Tue, 14 Oct 2025 16:34:15 +0000

Refactor statusbar to be configurable

Diffstat:
Mcmd/statusbar/main.go | 60++++++++++++------------------------------------------------
Mpkg/statusbar/datetime/datetime.go | 10+++++-----
Mpkg/statusbar/disk/disk.go | 5+++++
Mpkg/statusbar/eth/eth.go | 8++++++--
Mpkg/statusbar/power/power.go | 4++++
Mpkg/statusbar/temp/temp.go | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mpkg/statusbar/wlan/wlan.go | 15+++++++++++----
Mpkg/statusbar/wwan/wwan.go | 4++++
8 files changed, 130 insertions(+), 80 deletions(-)

diff --git a/cmd/statusbar/main.go b/cmd/statusbar/main.go @@ -8,20 +8,9 @@ import ( "path/filepath" "time" + "code.dwrz.net/src/cmd/statusbar/config" "code.dwrz.net/src/pkg/log" "code.dwrz.net/src/pkg/statusbar" - "code.dwrz.net/src/pkg/statusbar/cpu" - "code.dwrz.net/src/pkg/statusbar/datetime" - "code.dwrz.net/src/pkg/statusbar/disk" - "code.dwrz.net/src/pkg/statusbar/eth" - "code.dwrz.net/src/pkg/statusbar/light" - "code.dwrz.net/src/pkg/statusbar/memory" - "code.dwrz.net/src/pkg/statusbar/mic" - "code.dwrz.net/src/pkg/statusbar/power" - "code.dwrz.net/src/pkg/statusbar/temp" - "code.dwrz.net/src/pkg/statusbar/volume" - "code.dwrz.net/src/pkg/statusbar/wlan" - "code.dwrz.net/src/pkg/statusbar/wwan" ) var once = flag.Bool("once", false, "print output once") @@ -54,37 +43,15 @@ func main() { } defer flog.Close() - // Prepare the datetime format. - now := time.Now() - yearEnd := time.Date( - now.Year()+1, 1, 1, 0, 0, 0, 0, time.UTC, - ).AddDate(0, 0, -1) - - dfmt := fmt.Sprintf( - "+%%Y-%%m-%%d %%u/7 %%W/52 %%j/%d %%H:%%M %%Z", - yearEnd.YearDay(), - ) - + // Load the config and create the blocks. + cfg, blocks, err := config.Load() + if err != nil { + l.Error.Fatalf("failed to load config: %v", err) + } var bar = statusbar.New(statusbar.Parameters{ - Blocks: []statusbar.Block{ - cpu.New(), - temp.New(), - memory.New(), - disk.New("/", "/home"), - eth.New("eth0"), - wlan.New(), - wwan.New("wwan0"), - power.New(power.Path), - light.New(), - volume.New(), - mic.New(), - datetime.New(datetime.Parameters{ - Format: dfmt, - Timezone: "UTC", - }), - }, + Blocks: blocks, Log: log.New(flog), - Separator: "┃", + Separator: cfg.Separator, }) // Initial print. @@ -95,12 +62,9 @@ func main() { // Main loop. ticker := time.NewTicker(1 * time.Second) - for { - select { - case <-ticker.C: - // now := time.Now() - fmt.Println(bar.Render(ctx)) - // fmt.Println(time.Since(now)) - } + for range ticker.C { + // now := time.Now() + fmt.Println(bar.Render(ctx)) + // fmt.Println(time.Since(now)) } } diff --git a/pkg/statusbar/datetime/datetime.go b/pkg/statusbar/datetime/datetime.go @@ -13,13 +13,13 @@ type Block struct { timezone string } -type Parameters struct { - Format string - Label string - Timezone string +type Config struct { + Format string `json:"format"` + Label string `json:"label"` + Timezone string `json:"timezone"` } -func New(p Parameters) *Block { +func New(p Config) *Block { if p.Timezone == "" { p.Timezone = "UTC" } diff --git a/pkg/statusbar/disk/disk.go b/pkg/statusbar/disk/disk.go @@ -7,6 +7,11 @@ import ( "strings" ) +// Config holds the configuration for the disk block. +type Config struct { + Mounts []string `json:"mounts"` +} + type Block struct { mounts []string } diff --git a/pkg/statusbar/eth/eth.go b/pkg/statusbar/eth/eth.go @@ -6,6 +6,10 @@ import ( "net" ) +type Config struct { + Iface string `json:"iface"` +} + type Block struct { iface string } @@ -22,7 +26,7 @@ func (b *Block) Render(ctx context.Context) (string, error) { iface, err := net.InterfaceByName(b.iface) if err != nil { if err.Error() == "route ip+net: no such network interface" { - return fmt.Sprintf(" "), nil + return " ", nil } return "", fmt.Errorf( "failed to get interface %s: %v", b.iface, err, @@ -47,7 +51,7 @@ func (b *Block) Render(ctx context.Context) (string, error) { switch { case ip4 == "" && ip6 == "": - return fmt.Sprintf(" "), nil + return " ", nil case ip4 != "" && ip6 == "": return fmt.Sprintf(" %s", ip4), nil case ip4 == "" && ip6 != "": diff --git a/pkg/statusbar/power/power.go b/pkg/statusbar/power/power.go @@ -11,6 +11,10 @@ import ( "strings" ) +type Config struct { + Path string `json:"path"` +} + // https://www.kernel.org/doc/html/latest/power/power_supply_class.html const Path = "/sys/class/power_supply" diff --git a/pkg/statusbar/temp/temp.go b/pkg/statusbar/temp/temp.go @@ -5,12 +5,26 @@ import ( "encoding/json" "fmt" "os/exec" + "strings" ) -type Block struct{} +// Sensor describes a single temperature sensor to be read. +type Sensor struct { + Label string `json:"label"` + Path []string `json:"path"` +} + +// Config holds the configuration for the temp block. +type Config struct { + Sensors []Sensor `json:"sensors"` +} + +type Block struct { + config Config +} -func New() *Block { - return &Block{} +func New(c Config) *Block { + return &Block{config: c} } func (b *Block) Name() string { @@ -22,25 +36,73 @@ func (b *Block) Render(ctx context.Context) (string, error) { if err != nil { return "", fmt.Errorf("exec sensors failed: %v", err) } + if len(b.config.Sensors) == 0 { + return "", nil + } + + var parts = make([]string, 0, len(b.config.Sensors)) + for _, sensor := range b.config.Sensors { + temp, err := parse(out, sensor.Path) + if err != nil { + return "", fmt.Errorf( + "failed to get temp for %v: %w", + sensor.Path, err, + ) + } + + var renderedSensor string + if sensor.Label != "" { + renderedSensor = fmt.Sprintf( + "%s %.0f℃", sensor.Label, temp, + ) + } else { + renderedSensor = fmt.Sprintf("%.0f℃", temp) + } + parts = append(parts, renderedSensor) + } + + return strings.Join(parts, " "), nil +} + +// parse traverses the json data using the given path to find a temperature +// value. +func parse(data []byte, path []string) (float64, error) { + if len(path) == 0 { + return 0, fmt.Errorf("sensor path cannot be empty") + } + + // We start with the full JSON output. + var current = json.RawMessage(data) + + // For each key in the path, we dive one level deeper into the JSON + // structure. + for i, key := range path { + var m map[string]json.RawMessage + if err := json.Unmarshal(current, &m); err != nil { + return 0, fmt.Errorf( + "failed to unmarshal object at path segment %d ('%s'): %w", + i, key, err, + ) + } + + var ok bool + current, ok = m[key] + if !ok { + return 0, fmt.Errorf( + "key not found at path segment %d ('%s')", + i, key, + ) + } + } - var sensors = struct { - Thinkpad struct { - CPU struct { - Temp float64 `json:"temp1_input"` - } `json:"CPU"` - } `json:"thinkpad-isa-0000"` - NVME struct { - Composite struct { - Temp float64 `json:"temp1_input"` - } `json:"Composite"` - } `json:"nvme-pci-0400"` - }{} - if err := json.Unmarshal(out, &sensors); err != nil { - return "", fmt.Errorf("failed to json unmarshal: %v", err) + // After the loop, 'current' holds the raw JSON for the temperature + // value. + var temp float64 + if err := json.Unmarshal(current, &temp); err != nil { + return 0, fmt.Errorf( + "final value at path %v is not a number: %w", path, err, + ) } - return fmt.Sprintf( - " %.0f℃  %.0f℃", - sensors.Thinkpad.CPU.Temp, sensors.NVME.Composite.Temp, - ), nil + return temp, nil } diff --git a/pkg/statusbar/wlan/wlan.go b/pkg/statusbar/wlan/wlan.go @@ -9,10 +9,17 @@ import ( "strings" ) -type Block struct{} +// Config holds the configuration for the wlan block. +type Config struct { + Iface string `json:"iface"` +} + +type Block struct { + iface string +} -func New() *Block { - return &Block{} +func New(iface string) *Block { + return &Block{iface: iface} } func (b *Block) Name() string { @@ -22,7 +29,7 @@ func (b *Block) Name() string { // TODO: signal strength icon. func (b *Block) Render(ctx context.Context) (string, error) { out, err := exec.CommandContext( - ctx, "iwctl", "station", "wlan0", "show", + ctx, "iwctl", "station", b.iface, "show", ).Output() if err != nil { return "", fmt.Errorf("exec iwctl failed: %v", err) diff --git a/pkg/statusbar/wwan/wwan.go b/pkg/statusbar/wwan/wwan.go @@ -11,6 +11,10 @@ import ( "strings" ) +type Config struct { + Iface string `json:"iface"` +} + type Block struct { iface string }