collection of changes

updated default config with go functions

changed reader function implementations to use only a single function

capture outputs in byte arrays and store outputs as byte arrays instead of strings

add date reader

move some main function code to separate functions

move flags parsing to main
This commit is contained in:
frosty 2024-08-24 03:13:04 -04:00
parent 8c316dee38
commit 9ec3e94ae5
11 changed files with 442 additions and 420 deletions

View file

@ -23,30 +23,33 @@ import (
) )
var ( var (
delim = "] [" delim = []byte("] [")
prefix = "[" prefix = []byte("[")
suffix = "]" suffix = []byte("]")
) )
var modules = []Module{ var modules = []Module{
{ {
Func: readers.ReadExec("statusbar cpu"), Func: readers.ReadCpuUsage(),
Interval: 5 * time.Second, Interval: 5 * time.Second,
Template: `CPU {{printf "%.0f" .UsagePercent}}%`,
}, },
{ {
Func: readers.ReadExec("statusbar volume"), Func: readers.ReadBattery("BAT1"),
Interval: 60 * time.Second,
Template: "BAT {{.Capacity}}%",
},
{
Func: readers.ReadExec("monitors volume"),
Signal: 1, Signal: 1,
}, },
{ {
Func: readers.ReadExec("statusbar battery"), Func: readers.ReadDate("15:04:05"),
Interval: 60 * time.Second,
},
{
Func: readers.ReadExec("statusbar date"),
Interval: 1 * time.Second, Interval: 1 * time.Second,
}, },
{ {
Func: readers.ReadExec("statusbar loadavg"), Func: readers.ReadLoad(),
Interval: 5 * time.Second, Interval: 5 * time.Second,
Template: "{{.OneMinute}}",
}, },
} }

View file

@ -116,7 +116,8 @@ func BatteryTechnologyFromStr(technologyStr string) BatteryTechnology {
return TechnologyUnknown return TechnologyUnknown
} }
func readBattery(batteryName string) (interface{}, error) { func ReadBattery(batteryName string) func() (interface{}, error) {
return func() (interface{}, error) {
capacityPath := fmt.Sprintf("/sys/class/power_supply/%s/capacity", batteryName) capacityPath := fmt.Sprintf("/sys/class/power_supply/%s/capacity", batteryName)
statusPath := fmt.Sprintf("/sys/class/power_supply/%s/status", batteryName) statusPath := fmt.Sprintf("/sys/class/power_supply/%s/status", batteryName)
technologyPath := fmt.Sprintf("/sys/class/power_supply/%s/technology", batteryName) technologyPath := fmt.Sprintf("/sys/class/power_supply/%s/technology", batteryName)
@ -169,9 +170,4 @@ func readBattery(batteryName string) (interface{}, error) {
Technology: BatteryTechnologyFromStr(batteryTechnology), Technology: BatteryTechnologyFromStr(batteryTechnology),
}, nil }, nil
} }
func ReadBattery(batteryName string) func() (interface{}, error) {
return func() (interface{}, error) {
return readBattery(batteryName)
}
} }

View file

@ -25,7 +25,8 @@ import (
type CpuTemperatureInfo float32 type CpuTemperatureInfo float32
func readCpuTemperature(hwmonName, tempName string) (interface{}, error) { func ReadCpuTemperature(hwmonName, tempName string) func() (interface{}, error) {
return func() (interface{}, error) {
tempPath := fmt.Sprintf("/sys/class/hwmon/%s/%s_input", hwmonName, tempName) tempPath := fmt.Sprintf("/sys/class/hwmon/%s/%s_input", hwmonName, tempName)
file, err := os.Open(tempPath) file, err := os.Open(tempPath)
@ -49,9 +50,4 @@ func readCpuTemperature(hwmonName, tempName string) (interface{}, error) {
return CpuTemperatureInfo(cpuTemperature), nil return CpuTemperatureInfo(cpuTemperature), nil
} }
func ReadCpuTemperature(hwmonName, tempName string) func() (interface{}, error) {
return func() (interface{}, error) {
return readCpuTemperature(hwmonName, tempName)
}
} }

View file

@ -34,7 +34,8 @@ func (cu CpuUsageInfo) String() string {
return fmt.Sprintf("%d%%", uint(cu.UsagePercent)) return fmt.Sprintf("%d%%", uint(cu.UsagePercent))
} }
func readCpuUsage() (interface{}, error) { func ReadCpuUsage() func() (interface{}, error) {
return func() (interface{}, error) {
file, err := os.Open("/proc/stat") file, err := os.Open("/proc/stat")
if err != nil { if err != nil {
return CpuUsageInfo{}, err return CpuUsageInfo{}, err
@ -81,9 +82,4 @@ func readCpuUsage() (interface{}, error) {
UsagePercent: float64(cpuInUse) * 100 / float64(cpuTotal), UsagePercent: float64(cpuInUse) * 100 / float64(cpuTotal),
}, nil }, nil
} }
func ReadCpuUsage() func() (interface{}, error) {
return func() (interface{}, error) {
return readCpuUsage()
}
} }

30
lib/readers/date.go Normal file
View file

@ -0,0 +1,30 @@
// modbot is a system information agregator
// Copyright (C) 2024 frosty <inthishouseofcards@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package readers
import "time"
type DateInfo string
func ReadDate(format string) func() (interface{}, error) {
return func() (interface{}, error) {
date := time.Now()
// Reference: https://gosamples.dev/date-time-format-cheatsheet/
return date.Format(format), nil
}
}

View file

@ -25,7 +25,8 @@ import (
type ExecInfo string type ExecInfo string
func readExec(command string) (interface{}, error) { func ReadExec(command string) func() (interface{}, error) {
return func() (interface{}, error) {
args := []string{"sh", "-c", command} args := []string{"sh", "-c", command}
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
@ -36,10 +37,12 @@ func readExec(command string) (interface{}, error) {
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return ExecInfo(""), err return ExecInfo(""), err
} }
if cmd.ProcessState.ExitCode() != 0 { if cmd.ProcessState.ExitCode() != 0 {
return ExecInfo(""), errors.New("returned non-zero exit code") return ExecInfo(""), errors.New("returned non-zero exit code")
} }
if len(stderr.String()) > 0 {
return ExecInfo(""), errors.New("data in stderr found")
}
outputLines := strings.Split(stdout.String(), "\n") outputLines := strings.Split(stdout.String(), "\n")
if len(outputLines) == 0 { if len(outputLines) == 0 {
@ -48,11 +51,5 @@ func readExec(command string) (interface{}, error) {
outputString := outputLines[0] outputString := outputLines[0]
return ExecInfo(outputString), nil return ExecInfo(outputString), nil
}
func ReadExec(command string) func() (interface{}, error) {
return func() (interface{}, error) {
return readExec(command)
} }
} }

View file

@ -33,7 +33,8 @@ func (l LoadInfo) String() string {
return fmt.Sprintf("%v %v %v", l.OneMinute, l.FiveMinute, l.FifteenMinute) return fmt.Sprintf("%v %v %v", l.OneMinute, l.FiveMinute, l.FifteenMinute)
} }
func readLoad() (interface{}, error) { func ReadLoad() func() (interface{}, error) {
return func() (interface{}, error) {
const loadPath = "/proc/loadavg" const loadPath = "/proc/loadavg"
file, err := os.Open(loadPath) file, err := os.Open(loadPath)
@ -60,9 +61,4 @@ func readLoad() (interface{}, error) {
FifteenMinute: fields[2], FifteenMinute: fields[2],
}, nil }, nil
} }
func ReadLoad() func() (interface{}, error) {
return func() (interface{}, error) {
return readLoad()
}
} }

View file

@ -37,7 +37,8 @@ type MemoryInfo struct {
UsedPretty string UsedPretty string
} }
func readMemory() (interface{}, error) { func ReadMemory() func() (interface{}, error) {
return func() (interface{}, error) {
file, err := os.Open("/proc/meminfo") file, err := os.Open("/proc/meminfo")
if err != nil { if err != nil {
return MemoryInfo{}, err return MemoryInfo{}, err
@ -89,9 +90,4 @@ func readMemory() (interface{}, error) {
UsedPretty: ui.PrettifyKib(memUsed, 2), UsedPretty: ui.PrettifyKib(memUsed, 2),
}, nil }, nil
} }
func ReadMemory() func() (interface{}, error) {
return func() (interface{}, error) {
return readMemory()
}
} }

View file

@ -29,7 +29,8 @@ type OsInfo struct {
Version string Version string
} }
func readOs() (interface{}, error) { func ReadOs() func() (interface{}, error) {
return func() (interface{}, error) {
const osPath = "/lib/os-release" const osPath = "/lib/os-release"
file, err := os.Open(osPath) file, err := os.Open(osPath)
@ -70,9 +71,4 @@ func readOs() (interface{}, error) {
Version: osVersion, Version: osVersion,
}, nil }, nil
} }
func ReadOs() func() (interface{}, error) {
return func() (interface{}, error) {
return readOs()
}
} }

View file

@ -65,7 +65,8 @@ func (u UptimeInfo) String() string {
return builder.String() return builder.String()
} }
func readUptime() (interface{}, error) { func ReadUptime() func() (interface{}, error) {
return func() (interface{}, error) {
const uptimePath = "/proc/uptime" const uptimePath = "/proc/uptime"
file, err := os.Open(uptimePath) file, err := os.Open(uptimePath)
@ -92,9 +93,4 @@ func readUptime() (interface{}, error) {
return UptimeInfo(uptime), nil return UptimeInfo(uptime), nil
} }
func ReadUptime() func() (interface{}, error) {
return func() (interface{}, error) {
return readUptime()
}
} }

170
main.go
View file

@ -36,10 +36,11 @@ import (
var ( var (
updateChan = make(chan int) updateChan = make(chan int)
moduleOutputs = make([]string, len(modules)) moduleOutputs = make([][]byte, len(modules))
mutex sync.Mutex mutex sync.Mutex
lastOutput string
sigChan = make(chan os.Signal, 1024)
signalMap = make(map[os.Signal][]*Module)
// X connection data // X connection data
x *xgb.Conn x *xgb.Conn
@ -62,17 +63,16 @@ type Module struct {
func (m *Module) Run() { func (m *Module) Run() {
if m.pos < 0 || m.pos >= len(modules) { if m.pos < 0 || m.pos >= len(modules) {
log.Printf("invalid module index %d\n", m.pos) log.Printf("invalid module index: %d\n", m.pos)
return return
} }
var output bytes.Buffer
info, err := m.Func() info, err := m.Func()
if err != nil { if err != nil {
moduleOutputs[m.pos] = "failed" output.WriteString("failed")
return } else {
}
var output string
if m.Template != "" { if m.Template != "" {
// Parse the output and apply the provided template // Parse the output and apply the provided template
tmpl, err := template.New("module").Parse(m.Template) tmpl, err := template.New("module").Parse(m.Template)
@ -81,53 +81,24 @@ func (m *Module) Run() {
return return
} }
var buf bytes.Buffer if err := tmpl.Execute(&output, info); err != nil {
if err := tmpl.Execute(&buf, info); err != nil {
log.Printf("template execution error: %v\n", err) log.Printf("template execution error: %v\n", err)
return return
} }
output = buf.String()
} else { } else {
// Use the output as is // Use the output as is
output = fmt.Sprintf("%v", info) fmt.Fprintf(&output, "%v", info)
}
} }
mutex.Lock() mutex.Lock()
moduleOutputs[m.pos] = output moduleOutputs[m.pos] = output.Bytes()
updateChan <- 1 updateChan <- 1
mutex.Unlock() mutex.Unlock()
} }
func parseFlags() Flags { func (m *Module) Init(pos int) {
var flags Flags m.pos = pos
flag.BoolVar(&flags.SetXRootName, "x", false, "set x root window name")
flag.Parse()
return flags
}
func main() {
flags := parseFlags()
// Connect to X and get the root window if requested
if flags.SetXRootName {
var err error
x, err = xgb.NewConn()
if err != nil {
log.Fatalf("X connection failed: %s\n", err.Error())
}
root = xproto.Setup(x).DefaultScreen(x).Root
}
sigChan := make(chan os.Signal, 1024)
signalMap := make(map[os.Signal][]*Module)
// Initialize modules
for i := range modules {
go func(m *Module, i int) {
m.pos = i
if m.Signal != 0 { if m.Signal != 0 {
sig := syscall.Signal(34 + m.Signal) sig := syscall.Signal(34 + m.Signal)
@ -144,48 +115,97 @@ func main() {
m.Run() m.Run()
} }
} }
}(&modules[i], i)
} }
// Update output on difference func grabXRootWindow() (*xgb.Conn, xproto.Window, error) {
go func() { conn, err := xgb.NewConn()
for range updateChan { if err != nil {
mutex.Lock() return nil, 0, err
var combinedOutput string }
for i, output := range moduleOutputs {
if output == "" { root := xproto.Setup(conn).DefaultScreen(conn).Root
if conn == nil {
return nil, 0, fmt.Errorf("failed to create X connection")
}
return conn, root, nil
}
func createOutput(b *bytes.Buffer) {
b.Write(prefix)
first := true
for _, output := range moduleOutputs {
if output == nil {
continue continue
} }
if i > 0 { if !first {
combinedOutput += delim b.Write(delim)
} else {
first = false
} }
combinedOutput += output b.Write(output)
} }
combinedOutput = prefix + combinedOutput + suffix b.Write(suffix)
}
func monitorUpdates(setXRootName bool) {
var lastOutput []byte
var combinedOutput bytes.Buffer
for range updateChan {
mutex.Lock()
combinedOutput.Reset()
createOutput(&combinedOutput)
mutex.Unlock() mutex.Unlock()
// Output to either X root window name or stdout based on flags combinedOutputBytes := combinedOutput.Bytes()
if combinedOutput != lastOutput {
if flags.SetXRootName {
// Set the X root window name
outputBytes := []byte(combinedOutput)
xproto.ChangeProperty(x, xproto.PropModeReplace, root, xproto.AtomWmName, xproto.AtomString, 8, uint32(len(outputBytes)), outputBytes)
} else {
// Print to stdout
fmt.Printf("%v\n", combinedOutput)
}
lastOutput = combinedOutput
}
}
}()
// Handle module signals if !bytes.Equal(combinedOutputBytes, lastOutput) {
for sig := range sigChan { if setXRootName {
go func(sig *os.Signal) { // Set X root window name
ms := signalMap[*sig] xproto.ChangeProperty(x, xproto.PropModeReplace, root, xproto.AtomWmName, xproto.AtomString, 8, uint32(len(combinedOutputBytes)), combinedOutputBytes)
} else {
// Send to stdout
fmt.Printf("%s\n", combinedOutputBytes)
}
lastOutput = append([]byte(nil), combinedOutputBytes...)
}
}
}
func handleSignal(sig os.Signal) {
ms := signalMap[sig]
for _, m := range ms { for _, m := range ms {
go m.Run() go m.Run()
} }
}(&sig) }
func main() {
// Parse flags
var flags Flags
flag.BoolVar(&flags.SetXRootName, "x", false, "set x root window name")
flag.Parse()
// Grab X root window if requested
if flags.SetXRootName {
var err error
x, root, err = grabXRootWindow()
if err != nil {
log.Fatalf("error grabbing X root window: %v\n", err)
}
defer x.Close()
}
// Initialize modules
for i := range modules {
go modules[i].Init(i)
}
// Monitor changes to the combined output
go monitorUpdates(flags.SetXRootName)
// Handle signals sent for modules
for sig := range sigChan {
go handleSignal(sig)
} }
} }