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 (
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}}",
},
}

View file

@ -116,62 +116,58 @@ func BatteryTechnologyFromStr(technologyStr string) BatteryTechnology {
return TechnologyUnknown
}
func readBattery(batteryName string) (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)
capacityFile, err := os.Open(capacityPath)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to open %s: %w", capacityPath, err)
}
defer capacityFile.Close()
capacityScanner := bufio.NewScanner(capacityFile)
if !capacityScanner.Scan() {
return BatteryInfo{}, fmt.Errorf("failed to read from %s: %w", capacityPath, capacityScanner.Err())
}
statusFile, err := os.Open(statusPath)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to open %s: %w", statusPath, err)
}
defer statusFile.Close()
statusScanner := bufio.NewScanner(statusFile)
if !statusScanner.Scan() {
return BatteryInfo{}, fmt.Errorf("failed to read from %s: %w", statusPath, statusScanner.Err())
}
technologyFile, err := os.Open(technologyPath)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to open %s: %w", technologyPath, err)
}
defer technologyFile.Close()
technologyScanner := bufio.NewScanner(technologyFile)
if !technologyScanner.Scan() {
return BatteryInfo{}, fmt.Errorf("failed to read from %s: %w", technologyPath, technologyScanner.Err())
}
batteryCapacityStr := capacityScanner.Text()
batteryStatus := statusScanner.Text()
batteryTechnology := technologyScanner.Text()
batteryCapacity, err := strconv.ParseUint(batteryCapacityStr, 10, 8)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to parse capacity from %s: %w", capacityPath, err)
}
return BatteryInfo{
Capacity: uint8(batteryCapacity),
Status: BatteryStatusFromStr(batteryStatus),
Technology: BatteryTechnologyFromStr(batteryTechnology),
}, nil
}
func ReadBattery(batteryName string) func() (interface{}, error) {
return func() (interface{}, error) {
return readBattery(batteryName)
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)
capacityFile, err := os.Open(capacityPath)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to open %s: %w", capacityPath, err)
}
defer capacityFile.Close()
capacityScanner := bufio.NewScanner(capacityFile)
if !capacityScanner.Scan() {
return BatteryInfo{}, fmt.Errorf("failed to read from %s: %w", capacityPath, capacityScanner.Err())
}
statusFile, err := os.Open(statusPath)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to open %s: %w", statusPath, err)
}
defer statusFile.Close()
statusScanner := bufio.NewScanner(statusFile)
if !statusScanner.Scan() {
return BatteryInfo{}, fmt.Errorf("failed to read from %s: %w", statusPath, statusScanner.Err())
}
technologyFile, err := os.Open(technologyPath)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to open %s: %w", technologyPath, err)
}
defer technologyFile.Close()
technologyScanner := bufio.NewScanner(technologyFile)
if !technologyScanner.Scan() {
return BatteryInfo{}, fmt.Errorf("failed to read from %s: %w", technologyPath, technologyScanner.Err())
}
batteryCapacityStr := capacityScanner.Text()
batteryStatus := statusScanner.Text()
batteryTechnology := technologyScanner.Text()
batteryCapacity, err := strconv.ParseUint(batteryCapacityStr, 10, 8)
if err != nil {
return BatteryInfo{}, fmt.Errorf("failed to parse capacity from %s: %w", capacityPath, err)
}
return BatteryInfo{
Capacity: uint8(batteryCapacity),
Status: BatteryStatusFromStr(batteryStatus),
Technology: BatteryTechnologyFromStr(batteryTechnology),
}, nil
}
}

View file

@ -25,33 +25,29 @@ import (
type CpuTemperatureInfo float32
func readCpuTemperature(hwmonName, tempName string) (interface{}, error) {
tempPath := fmt.Sprintf("/sys/class/hwmon/%s/%s_input", hwmonName, tempName)
file, err := os.Open(tempPath)
if err != nil {
return 0, fmt.Errorf("failed to open %s: %w", tempPath, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
if !scanner.Scan() {
return 0, fmt.Errorf("failed to read from %s: %w", tempPath, scanner.Err())
}
line := scanner.Text()
cpuTemperatureMdeg, err := strconv.ParseUint(line, 10, 32)
if err != nil {
return 0, fmt.Errorf("failed to parse cpu temperature from %s: %w", tempPath, err)
}
cpuTemperature := float32(cpuTemperatureMdeg) / 1000
return CpuTemperatureInfo(cpuTemperature), nil
}
func ReadCpuTemperature(hwmonName, tempName string) func() (interface{}, error) {
return func() (interface{}, error) {
return readCpuTemperature(hwmonName, tempName)
tempPath := fmt.Sprintf("/sys/class/hwmon/%s/%s_input", hwmonName, tempName)
file, err := os.Open(tempPath)
if err != nil {
return 0, fmt.Errorf("failed to open %s: %w", tempPath, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
if !scanner.Scan() {
return 0, fmt.Errorf("failed to read from %s: %w", tempPath, scanner.Err())
}
line := scanner.Text()
cpuTemperatureMdeg, err := strconv.ParseUint(line, 10, 32)
if err != nil {
return 0, fmt.Errorf("failed to parse cpu temperature from %s: %w", tempPath, err)
}
cpuTemperature := float32(cpuTemperatureMdeg) / 1000
return CpuTemperatureInfo(cpuTemperature), nil
}
}

View file

@ -34,56 +34,52 @@ func (cu CpuUsageInfo) String() string {
return fmt.Sprintf("%d%%", uint(cu.UsagePercent))
}
func readCpuUsage() (interface{}, error) {
file, err := os.Open("/proc/stat")
if err != nil {
return CpuUsageInfo{}, err
}
defer file.Close()
var cpuInUse, cpuTotal uint64
scanner := bufio.NewScanner(file)
scanner.Scan()
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "cpu") {
break
}
var cpuN string
var cpuUser, cpuNice, cpuSystem, cpuIdle, cpuIoWait, cpuIrq, cpuSoftIrq uint64
_, err := fmt.Sscanf(line, "cpu%s %d %d %d %d %d %d %d",
&cpuN, &cpuUser, &cpuNice, &cpuSystem, &cpuIdle, &cpuIoWait, &cpuIrq, &cpuSoftIrq)
if err != nil {
return CpuUsageInfo{}, fmt.Errorf("failed to parse CPU stats: %w", err)
}
inUse := cpuUser + cpuNice + cpuSystem
total := inUse + cpuIdle + cpuIoWait + cpuIrq + cpuSoftIrq
cpuInUse += inUse
cpuTotal += total
}
if err := scanner.Err(); err != nil {
return CpuUsageInfo{}, err
}
if cpuTotal == 0 {
return CpuUsageInfo{}, errors.New("no CPU stats found")
}
return CpuUsageInfo{
InUse: cpuInUse,
Total: cpuTotal,
UsagePercent: float64(cpuInUse) * 100 / float64(cpuTotal),
}, nil
}
func ReadCpuUsage() func() (interface{}, error) {
return func() (interface{}, error) {
return readCpuUsage()
file, err := os.Open("/proc/stat")
if err != nil {
return CpuUsageInfo{}, err
}
defer file.Close()
var cpuInUse, cpuTotal uint64
scanner := bufio.NewScanner(file)
scanner.Scan()
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "cpu") {
break
}
var cpuN string
var cpuUser, cpuNice, cpuSystem, cpuIdle, cpuIoWait, cpuIrq, cpuSoftIrq uint64
_, err := fmt.Sscanf(line, "cpu%s %d %d %d %d %d %d %d",
&cpuN, &cpuUser, &cpuNice, &cpuSystem, &cpuIdle, &cpuIoWait, &cpuIrq, &cpuSoftIrq)
if err != nil {
return CpuUsageInfo{}, fmt.Errorf("failed to parse CPU stats: %w", err)
}
inUse := cpuUser + cpuNice + cpuSystem
total := inUse + cpuIdle + cpuIoWait + cpuIrq + cpuSoftIrq
cpuInUse += inUse
cpuTotal += total
}
if err := scanner.Err(); err != nil {
return CpuUsageInfo{}, err
}
if cpuTotal == 0 {
return CpuUsageInfo{}, errors.New("no CPU stats found")
}
return CpuUsageInfo{
InUse: cpuInUse,
Total: cpuTotal,
UsagePercent: float64(cpuInUse) * 100 / float64(cpuTotal),
}, nil
}
}

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,34 +25,31 @@ import (
type ExecInfo string
func readExec(command string) (interface{}, error) {
args := []string{"sh", "-c", command}
var stdout, stderr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return ExecInfo(""), err
}
if cmd.ProcessState.ExitCode() != 0 {
return ExecInfo(""), errors.New("returned non-zero exit code")
}
outputLines := strings.Split(stdout.String(), "\n")
if len(outputLines) == 0 {
return ExecInfo(""), nil
}
outputString := outputLines[0]
return ExecInfo(outputString), nil
}
func ReadExec(command string) func() (interface{}, error) {
return func() (interface{}, error) {
return readExec(command)
args := []string{"sh", "-c", command}
var stdout, stderr bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
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 {
return ExecInfo(""), nil
}
outputString := outputLines[0]
return ExecInfo(outputString), nil
}
}

View file

@ -33,36 +33,32 @@ func (l LoadInfo) String() string {
return fmt.Sprintf("%v %v %v", l.OneMinute, l.FiveMinute, l.FifteenMinute)
}
func readLoad() (interface{}, error) {
const loadPath = "/proc/loadavg"
file, err := os.Open(loadPath)
if err != nil {
return LoadInfo{}, fmt.Errorf("failed to open %s: %w", loadPath, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Scan()
if err := scanner.Err(); err != nil {
return LoadInfo{}, fmt.Errorf("failed to read from %s: %w", loadPath, err)
}
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 3 {
return LoadInfo{}, fmt.Errorf("unexpected format in %s: %s", loadPath, line)
}
return LoadInfo{
OneMinute: fields[0],
FiveMinute: fields[1],
FifteenMinute: fields[2],
}, nil
}
func ReadLoad() func() (interface{}, error) {
return func() (interface{}, error) {
return readLoad()
const loadPath = "/proc/loadavg"
file, err := os.Open(loadPath)
if err != nil {
return LoadInfo{}, fmt.Errorf("failed to open %s: %w", loadPath, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Scan()
if err := scanner.Err(); err != nil {
return LoadInfo{}, fmt.Errorf("failed to read from %s: %w", loadPath, err)
}
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 3 {
return LoadInfo{}, fmt.Errorf("unexpected format in %s: %s", loadPath, line)
}
return LoadInfo{
OneMinute: fields[0],
FiveMinute: fields[1],
FifteenMinute: fields[2],
}, nil
}
}

View file

@ -37,61 +37,57 @@ type MemoryInfo struct {
UsedPretty string
}
func readMemory() (interface{}, error) {
file, err := os.Open("/proc/meminfo")
if err != nil {
return MemoryInfo{}, err
}
defer file.Close()
var memTotal, memAvailable, memUsed uint64
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
columns := strings.Fields(line)
if len(columns) < 2 {
continue
}
value, err := strconv.ParseUint(columns[1], 10, 64)
if err != nil {
return MemoryInfo{}, fmt.Errorf("failed to parse memory value: %w", err)
}
switch {
case strings.HasPrefix(line, "MemTotal:"):
memTotal = value
case strings.HasPrefix(line, "MemAvailable:"):
memAvailable = value
}
}
if err := scanner.Err(); err != nil {
return MemoryInfo{}, err
}
if memTotal == 0 {
return MemoryInfo{}, errors.New("missing MemTotal")
}
if memAvailable == 0 {
return MemoryInfo{}, errors.New("missing MemAvailable")
}
memUsed = memTotal - memAvailable
return MemoryInfo{
Total: memTotal,
Available: memAvailable,
Used: memUsed,
TotalPretty: ui.PrettifyKib(memTotal, 2),
AvailablePretty: ui.PrettifyKib(memAvailable, 2),
UsedPretty: ui.PrettifyKib(memUsed, 2),
}, nil
}
func ReadMemory() func() (interface{}, error) {
return func() (interface{}, error) {
return readMemory()
file, err := os.Open("/proc/meminfo")
if err != nil {
return MemoryInfo{}, err
}
defer file.Close()
var memTotal, memAvailable, memUsed uint64
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
columns := strings.Fields(line)
if len(columns) < 2 {
continue
}
value, err := strconv.ParseUint(columns[1], 10, 64)
if err != nil {
return MemoryInfo{}, fmt.Errorf("failed to parse memory value: %w", err)
}
switch {
case strings.HasPrefix(line, "MemTotal:"):
memTotal = value
case strings.HasPrefix(line, "MemAvailable:"):
memAvailable = value
}
}
if err := scanner.Err(); err != nil {
return MemoryInfo{}, err
}
if memTotal == 0 {
return MemoryInfo{}, errors.New("missing MemTotal")
}
if memAvailable == 0 {
return MemoryInfo{}, errors.New("missing MemAvailable")
}
memUsed = memTotal - memAvailable
return MemoryInfo{
Total: memTotal,
Available: memAvailable,
Used: memUsed,
TotalPretty: ui.PrettifyKib(memTotal, 2),
AvailablePretty: ui.PrettifyKib(memAvailable, 2),
UsedPretty: ui.PrettifyKib(memUsed, 2),
}, nil
}
}

View file

@ -29,50 +29,46 @@ type OsInfo struct {
Version string
}
func readOs() (interface{}, error) {
const osPath = "/lib/os-release"
file, err := os.Open(osPath)
if err != nil {
return OsInfo{}, fmt.Errorf("failed to open %s: %w", osPath, err)
}
defer file.Close()
var osName, osPrettyName, osVersion string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
line = strings.ReplaceAll(line, "\"", "")
fields := strings.SplitN(line, "=", 2)
if len(fields) < 2 {
return OsInfo{}, fmt.Errorf("unexpected format in %s: %s", osPath, line)
}
switch {
case strings.HasPrefix(line, "NAME"):
osName = fields[1]
case strings.HasPrefix(line, "PRETTY_NAME"):
osPrettyName = fields[1]
case strings.HasPrefix(line, "VERSION_ID"):
osVersion = fields[1]
}
}
if err := scanner.Err(); err != nil {
return OsInfo{}, fmt.Errorf("failed to read from %s: %w", osPath, err)
}
return OsInfo{
Name: osName,
PrettyName: osPrettyName,
Version: osVersion,
}, nil
}
func ReadOs() func() (interface{}, error) {
return func() (interface{}, error) {
return readOs()
const osPath = "/lib/os-release"
file, err := os.Open(osPath)
if err != nil {
return OsInfo{}, fmt.Errorf("failed to open %s: %w", osPath, err)
}
defer file.Close()
var osName, osPrettyName, osVersion string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
line = strings.ReplaceAll(line, "\"", "")
fields := strings.SplitN(line, "=", 2)
if len(fields) < 2 {
return OsInfo{}, fmt.Errorf("unexpected format in %s: %s", osPath, line)
}
switch {
case strings.HasPrefix(line, "NAME"):
osName = fields[1]
case strings.HasPrefix(line, "PRETTY_NAME"):
osPrettyName = fields[1]
case strings.HasPrefix(line, "VERSION_ID"):
osVersion = fields[1]
}
}
if err := scanner.Err(); err != nil {
return OsInfo{}, fmt.Errorf("failed to read from %s: %w", osPath, err)
}
return OsInfo{
Name: osName,
PrettyName: osPrettyName,
Version: osVersion,
}, nil
}
}

View file

@ -65,36 +65,32 @@ func (u UptimeInfo) String() string {
return builder.String()
}
func readUptime() (interface{}, error) {
const uptimePath = "/proc/uptime"
file, err := os.Open(uptimePath)
if err != nil {
return 0, fmt.Errorf("failed to open %s: %w", uptimePath, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
if !scanner.Scan() {
return 0, fmt.Errorf("failed to read from %s: %w", uptimePath, scanner.Err())
}
line := scanner.Text()
fields := strings.SplitN(line, ".", 2)
if len(fields) < 1 {
return 0, fmt.Errorf("unexpected format in %s: %s", uptimePath, line)
}
uptime, err := strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse uptime from %s: %w", uptimePath, err)
}
return UptimeInfo(uptime), nil
}
func ReadUptime() func() (interface{}, error) {
return func() (interface{}, error) {
return readUptime()
const uptimePath = "/proc/uptime"
file, err := os.Open(uptimePath)
if err != nil {
return 0, fmt.Errorf("failed to open %s: %w", uptimePath, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
if !scanner.Scan() {
return 0, fmt.Errorf("failed to read from %s: %w", uptimePath, scanner.Err())
}
line := scanner.Text()
fields := strings.SplitN(line, ".", 2)
if len(fields) < 1 {
return 0, fmt.Errorf("unexpected format in %s: %s", uptimePath, line)
}
uptime, err := strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse uptime from %s: %w", uptimePath, err)
}
return UptimeInfo(uptime), nil
}
}

214
main.go
View file

@ -36,10 +36,11 @@ import (
var (
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 *xgb.Conn
@ -62,130 +63,149 @@ 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
if m.Template != "" {
// Parse the output and apply the provided template
tmpl, err := template.New("module").Parse(m.Template)
if err != nil {
log.Printf("template parsing error: %v\n", err)
return
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, info); err != nil {
log.Printf("template execution error: %v\n", err)
return
}
output = buf.String()
output.WriteString("failed")
} else {
// Use the output as is
output = fmt.Sprintf("%v", info)
if m.Template != "" {
// Parse the output and apply the provided template
tmpl, err := template.New("module").Parse(m.Template)
if err != nil {
log.Printf("template parsing error: %v\n", err)
return
}
if err := tmpl.Execute(&output, info); err != nil {
log.Printf("template execution error: %v\n", err)
return
}
} else {
// Use the output as is
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")
func (m *Module) Init(pos int) {
m.pos = pos
flag.Parse()
if m.Signal != 0 {
sig := syscall.Signal(34 + m.Signal)
if _, exists := signalMap[sig]; !exists {
signal.Notify(sigChan, sig)
}
signalMap[sig] = append(signalMap[sig], m)
}
return flags
m.Run()
if m.Interval > 0 {
for {
time.Sleep(m.Interval)
m.Run()
}
}
}
func grabXRootWindow() (*xgb.Conn, xproto.Window, error) {
conn, err := xgb.NewConn()
if err != nil {
return nil, 0, err
}
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 !first {
b.Write(delim)
} else {
first = false
}
b.Write(output)
}
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()
combinedOutputBytes := combinedOutput.Bytes()
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()
}
}
func main() {
flags := parseFlags()
// Parse flags
var flags Flags
flag.BoolVar(&flags.SetXRootName, "x", false, "set x root window name")
flag.Parse()
// Connect to X and get the root window if requested
// Grab X root window if requested
if flags.SetXRootName {
var err error
x, err = xgb.NewConn()
x, root, err = grabXRootWindow()
if err != nil {
log.Fatalf("X connection failed: %s\n", err.Error())
log.Fatalf("error grabbing X root window: %v\n", err)
}
root = xproto.Setup(x).DefaultScreen(x).Root
defer x.Close()
}
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 {
sig := syscall.Signal(34 + m.Signal)
if _, exists := signalMap[sig]; !exists {
signal.Notify(sigChan, sig)
}
signalMap[sig] = append(signalMap[sig], m)
}
m.Run()
if m.Interval > 0 {
for {
time.Sleep(m.Interval)
m.Run()
}
}
}(&modules[i], i)
go modules[i].Init(i)
}
// Update output on difference
go func() {
for range updateChan {
mutex.Lock()
var combinedOutput string
for i, output := range moduleOutputs {
if output == "" {
continue
}
if i > 0 {
combinedOutput += delim
}
combinedOutput += output
}
combinedOutput = prefix + combinedOutput + suffix
mutex.Unlock()
// Monitor changes to the combined output
go monitorUpdates(flags.SetXRootName)
// 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
}
}
}()
// Handle module signals
// Handle signals sent for modules
for sig := range sigChan {
go func(sig *os.Signal) {
ms := signalMap[*sig]
for _, m := range ms {
go m.Run()
}
}(&sig)
go handleSignal(sig)
}
}