2024-08-14 12:28:29 -04:00
// modbot is a system information agregator
2024-07-30 04:02:05 -04:00
// 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
2024-08-14 16:19:18 -04:00
// along with this prograb. If not, see <https://www.gnu.org/licenses/>.
2024-07-30 04:02:05 -04:00
2024-08-20 18:48:59 -04:00
// Credit to gocaudices (https://github.com/LordRusk/gocaudices) for the general outline of how to create the goroutines necessary, and for the X connection code.
2024-07-30 04:02:05 -04:00
package main
import (
2024-08-14 16:19:18 -04:00
"bytes"
2024-08-20 18:33:37 -04:00
"flag"
2024-07-30 04:02:05 -04:00
"fmt"
2024-08-14 16:19:18 -04:00
"html/template"
2024-07-30 04:02:05 -04:00
"log"
2024-08-14 16:19:18 -04:00
"os"
"os/signal"
"sync"
"syscall"
"time"
2024-08-20 18:33:37 -04:00
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
2024-07-30 04:02:05 -04:00
)
2024-08-14 16:19:18 -04:00
var (
updateChan = make ( chan int )
moduleOutputs = make ( [ ] string , len ( modules ) )
mutex sync . Mutex
lastOutput string
2024-08-20 18:33:37 -04:00
// X connection data
x * xgb . Conn
root xproto . Window
2024-07-30 04:14:13 -04:00
)
2024-08-20 18:33:37 -04:00
type Flags struct {
SetXRootName bool
}
2024-08-14 16:19:18 -04:00
type Module struct {
Func func ( ) ( interface { } , error )
Interval time . Duration
Template string
Signal int
2024-07-30 04:02:05 -04:00
2024-08-14 16:19:18 -04:00
// Internal
pos int
}
func ( m * Module ) Run ( ) {
if m . pos < 0 || m . pos >= len ( modules ) {
log . Printf ( "invalid module index %d\n" , m . pos )
return
2024-07-30 04:02:05 -04:00
}
2024-08-14 16:19:18 -04:00
info , err := m . Func ( )
2024-07-30 04:02:05 -04:00
if err != nil {
2024-08-14 16:19:18 -04:00
moduleOutputs [ m . pos ] = "failed"
return
2024-07-30 04:02:05 -04:00
}
2024-08-14 16:19:18 -04:00
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 ( )
} else {
// Use the output as is
output = fmt . Sprintf ( "%v" , info )
2024-08-14 12:28:29 -04:00
}
2024-08-14 16:19:18 -04:00
mutex . Lock ( )
moduleOutputs [ m . pos ] = output
updateChan <- 1
mutex . Unlock ( )
}
2024-08-20 18:33:37 -04:00
func parseFlags ( ) Flags {
var flags Flags
flag . BoolVar ( & flags . SetXRootName , "x" , false , "set x root window name" )
flag . Parse ( )
return flags
}
2024-08-14 16:19:18 -04:00
func main ( ) {
2024-08-20 18:33:37 -04:00
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
}
2024-08-14 16:19:18 -04:00
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 )
2024-08-14 12:28:29 -04:00
}
2024-08-14 16:19:18 -04:00
// Update output on difference
go func ( ) {
for range updateChan {
mutex . Lock ( )
var combinedOutput string
for i , output := range moduleOutputs {
if i > 0 {
combinedOutput += delim
}
combinedOutput += output
}
combinedOutput = prefix + combinedOutput + suffix
mutex . Unlock ( )
2024-08-14 12:28:29 -04:00
2024-08-20 18:33:37 -04:00
// Output to either X root window name or stdout based on flags
2024-08-14 16:19:18 -04:00
if combinedOutput != lastOutput {
2024-08-20 18:33:37 -04:00
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 )
}
2024-08-14 16:19:18 -04:00
lastOutput = combinedOutput
}
}
} ( )
// Handle module signals
for sig := range sigChan {
go func ( sig * os . Signal ) {
ms := signalMap [ * sig ]
for _ , m := range ms {
go m . Run ( )
}
} ( & sig )
2024-08-14 12:28:29 -04:00
}
2024-07-30 04:02:05 -04:00
}