mirror of
https://codeberg.org/frosty/modbot.git
synced 2024-09-19 03:36:35 -04:00
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:
parent
8c316dee38
commit
9ec3e94ae5
25
config.go
25
config.go
|
@ -23,30 +23,33 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
delim = "] ["
|
||||
prefix = "["
|
||||
suffix = "]"
|
||||
delim = []byte("] [")
|
||||
prefix = []byte("[")
|
||||
suffix = []byte("]")
|
||||
)
|
||||
|
||||
var modules = []Module{
|
||||
{
|
||||
Func: readers.ReadExec("statusbar cpu"),
|
||||
Func: readers.ReadCpuUsage(),
|
||||
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,
|
||||
},
|
||||
{
|
||||
Func: readers.ReadExec("statusbar battery"),
|
||||
Interval: 60 * time.Second,
|
||||
},
|
||||
{
|
||||
Func: readers.ReadExec("statusbar date"),
|
||||
Func: readers.ReadDate("15:04:05"),
|
||||
Interval: 1 * time.Second,
|
||||
},
|
||||
{
|
||||
Func: readers.ReadExec("statusbar loadavg"),
|
||||
Func: readers.ReadLoad(),
|
||||
Interval: 5 * time.Second,
|
||||
Template: "{{.OneMinute}}",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -116,7 +116,8 @@ func BatteryTechnologyFromStr(technologyStr string) BatteryTechnology {
|
|||
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)
|
||||
statusPath := fmt.Sprintf("/sys/class/power_supply/%s/status", batteryName)
|
||||
technologyPath := fmt.Sprintf("/sys/class/power_supply/%s/technology", batteryName)
|
||||
|
@ -168,10 +169,5 @@ func readBattery(batteryName string) (interface{}, error) {
|
|||
Status: BatteryStatusFromStr(batteryStatus),
|
||||
Technology: BatteryTechnologyFromStr(batteryTechnology),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadBattery(batteryName string) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readBattery(batteryName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ import (
|
|||
|
||||
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)
|
||||
|
||||
file, err := os.Open(tempPath)
|
||||
|
@ -48,10 +49,5 @@ func readCpuTemperature(hwmonName, tempName string) (interface{}, error) {
|
|||
cpuTemperature := float32(cpuTemperatureMdeg) / 1000
|
||||
|
||||
return CpuTemperatureInfo(cpuTemperature), nil
|
||||
}
|
||||
|
||||
func ReadCpuTemperature(hwmonName, tempName string) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readCpuTemperature(hwmonName, tempName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ func (cu CpuUsageInfo) String() string {
|
|||
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")
|
||||
if err != nil {
|
||||
return CpuUsageInfo{}, err
|
||||
|
@ -80,10 +81,5 @@ func readCpuUsage() (interface{}, error) {
|
|||
Total: cpuTotal,
|
||||
UsagePercent: float64(cpuInUse) * 100 / float64(cpuTotal),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadCpuUsage() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readCpuUsage()
|
||||
}
|
||||
}
|
||||
|
|
30
lib/readers/date.go
Normal file
30
lib/readers/date.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -25,7 +25,8 @@ import (
|
|||
|
||||
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}
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
|
@ -36,10 +37,12 @@ func readExec(command string) (interface{}, error) {
|
|||
if err := cmd.Run(); err != nil {
|
||||
return ExecInfo(""), err
|
||||
}
|
||||
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
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")
|
||||
if len(outputLines) == 0 {
|
||||
|
@ -48,11 +51,5 @@ func readExec(command string) (interface{}, error) {
|
|||
|
||||
outputString := outputLines[0]
|
||||
return ExecInfo(outputString), nil
|
||||
|
||||
}
|
||||
|
||||
func ReadExec(command string) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readExec(command)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ func (l LoadInfo) String() string {
|
|||
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"
|
||||
|
||||
file, err := os.Open(loadPath)
|
||||
|
@ -59,10 +60,5 @@ func readLoad() (interface{}, error) {
|
|||
FiveMinute: fields[1],
|
||||
FifteenMinute: fields[2],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadLoad() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readLoad()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ type MemoryInfo struct {
|
|||
UsedPretty string
|
||||
}
|
||||
|
||||
func readMemory() (interface{}, error) {
|
||||
func ReadMemory() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
file, err := os.Open("/proc/meminfo")
|
||||
if err != nil {
|
||||
return MemoryInfo{}, err
|
||||
|
@ -88,10 +89,5 @@ func readMemory() (interface{}, error) {
|
|||
AvailablePretty: ui.PrettifyKib(memAvailable, 2),
|
||||
UsedPretty: ui.PrettifyKib(memUsed, 2),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadMemory() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readMemory()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ type OsInfo struct {
|
|||
Version string
|
||||
}
|
||||
|
||||
func readOs() (interface{}, error) {
|
||||
func ReadOs() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
const osPath = "/lib/os-release"
|
||||
|
||||
file, err := os.Open(osPath)
|
||||
|
@ -69,10 +70,5 @@ func readOs() (interface{}, error) {
|
|||
PrettyName: osPrettyName,
|
||||
Version: osVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadOs() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readOs()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,8 @@ func (u UptimeInfo) String() string {
|
|||
return builder.String()
|
||||
}
|
||||
|
||||
func readUptime() (interface{}, error) {
|
||||
func ReadUptime() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
const uptimePath = "/proc/uptime"
|
||||
|
||||
file, err := os.Open(uptimePath)
|
||||
|
@ -91,10 +92,5 @@ func readUptime() (interface{}, error) {
|
|||
}
|
||||
|
||||
return UptimeInfo(uptime), nil
|
||||
}
|
||||
|
||||
func ReadUptime() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return readUptime()
|
||||
}
|
||||
}
|
||||
|
|
170
main.go
170
main.go
|
@ -36,10 +36,11 @@ import (
|
|||
|
||||
var (
|
||||
updateChan = make(chan int)
|
||||
moduleOutputs = make([]string, len(modules))
|
||||
|
||||
moduleOutputs = make([][]byte, len(modules))
|
||||
mutex sync.Mutex
|
||||
lastOutput string
|
||||
|
||||
sigChan = make(chan os.Signal, 1024)
|
||||
signalMap = make(map[os.Signal][]*Module)
|
||||
|
||||
// X connection data
|
||||
x *xgb.Conn
|
||||
|
@ -62,17 +63,16 @@ type Module struct {
|
|||
|
||||
func (m *Module) Run() {
|
||||
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
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
|
||||
info, err := m.Func()
|
||||
if err != nil {
|
||||
moduleOutputs[m.pos] = "failed"
|
||||
return
|
||||
}
|
||||
|
||||
var output string
|
||||
output.WriteString("failed")
|
||||
} else {
|
||||
if m.Template != "" {
|
||||
// Parse the output and apply the provided template
|
||||
tmpl, err := template.New("module").Parse(m.Template)
|
||||
|
@ -81,53 +81,24 @@ func (m *Module) Run() {
|
|||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, info); err != nil {
|
||||
if err := tmpl.Execute(&output, info); err != nil {
|
||||
log.Printf("template execution error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
output = buf.String()
|
||||
} else {
|
||||
// Use the output as is
|
||||
output = fmt.Sprintf("%v", info)
|
||||
fmt.Fprintf(&output, "%v", info)
|
||||
}
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
moduleOutputs[m.pos] = output
|
||||
moduleOutputs[m.pos] = output.Bytes()
|
||||
updateChan <- 1
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
func parseFlags() Flags {
|
||||
var flags Flags
|
||||
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
|
||||
func (m *Module) Init(pos int) {
|
||||
m.pos = pos
|
||||
|
||||
if m.Signal != 0 {
|
||||
sig := syscall.Signal(34 + m.Signal)
|
||||
|
@ -144,48 +115,97 @@ func main() {
|
|||
m.Run()
|
||||
}
|
||||
}
|
||||
}(&modules[i], i)
|
||||
}
|
||||
|
||||
func grabXRootWindow() (*xgb.Conn, xproto.Window, error) {
|
||||
conn, err := xgb.NewConn()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Update output on difference
|
||||
go func() {
|
||||
for range updateChan {
|
||||
mutex.Lock()
|
||||
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
|
||||
}
|
||||
if i > 0 {
|
||||
combinedOutput += delim
|
||||
if !first {
|
||||
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()
|
||||
|
||||
// Output to either X root window name or stdout based on flags
|
||||
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
|
||||
}
|
||||
}
|
||||
}()
|
||||
combinedOutputBytes := combinedOutput.Bytes()
|
||||
|
||||
// Handle module signals
|
||||
for sig := range sigChan {
|
||||
go func(sig *os.Signal) {
|
||||
ms := signalMap[*sig]
|
||||
if !bytes.Equal(combinedOutputBytes, lastOutput) {
|
||||
if setXRootName {
|
||||
// Set X root window name
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue