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 )
2024-08-24 03:13:04 -04:00
moduleOutputs = make ( [ ] [ ] byte , len ( modules ) )
mutex sync . Mutex
2024-08-14 16:19:18 -04:00
2024-08-24 03:13:04 -04:00
sigChan = make ( chan os . Signal , 1024 )
signalMap = make ( map [ os . Signal ] [ ] * Module )
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 ) {
2024-08-24 03:13:04 -04:00
log . Printf ( "invalid module index: %d\n" , m . pos )
2024-08-14 16:19:18 -04:00
return
2024-07-30 04:02:05 -04:00
}
2024-08-24 03:13:04 -04:00
var output bytes . Buffer
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-24 03:13:04 -04:00
output . WriteString ( "failed" )
} else {
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 )
}
2024-07-30 04:02:05 -04:00
}
2024-08-14 16:19:18 -04:00
2024-08-24 03:13:04 -04:00
mutex . Lock ( )
moduleOutputs [ m . pos ] = output . Bytes ( )
updateChan <- 1
mutex . Unlock ( )
}
func ( m * Module ) Init ( pos int ) {
m . pos = pos
if m . Signal != 0 {
sig := syscall . Signal ( 34 + m . Signal )
if _ , exists := signalMap [ sig ] ; ! exists {
signal . Notify ( sigChan , sig )
2024-08-14 16:19:18 -04:00
}
2024-08-24 03:13:04 -04:00
signalMap [ sig ] = append ( signalMap [ sig ] , m )
}
2024-08-14 16:19:18 -04:00
2024-08-24 03:13:04 -04:00
m . Run ( )
if m . Interval > 0 {
for {
time . Sleep ( m . Interval )
m . Run ( )
2024-08-14 16:19:18 -04:00
}
2024-08-24 03:13:04 -04:00
}
}
2024-08-14 16:19:18 -04:00
2024-08-24 03:13:04 -04:00
func grabXRootWindow ( ) ( * xgb . Conn , xproto . Window , error ) {
conn , err := xgb . NewConn ( )
if err != nil {
return nil , 0 , err
2024-08-14 12:28:29 -04:00
}
2024-08-24 03:13:04 -04:00
root := xproto . Setup ( conn ) . DefaultScreen ( conn ) . Root
if conn == nil {
return nil , 0 , fmt . Errorf ( "failed to create X connection" )
}
return conn , root , nil
2024-08-14 16:19:18 -04:00
}
2024-08-24 03:13:04 -04:00
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 )
}
2024-08-20 18:33:37 -04:00
2024-08-24 03:13:04 -04:00
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 ... )
}
}
}
2024-08-20 18:33:37 -04:00
2024-08-24 03:13:04 -04:00
func handleSignal ( sig os . Signal ) {
ms := signalMap [ sig ]
for _ , m := range ms {
go m . Run ( )
}
2024-08-20 18:33:37 -04:00
}
2024-08-14 16:19:18 -04:00
func main ( ) {
2024-08-24 03:13:04 -04:00
// Parse flags
var flags Flags
flag . BoolVar ( & flags . SetXRootName , "x" , false , "set x root window name" )
flag . Parse ( )
2024-08-20 18:33:37 -04:00
2024-08-24 03:13:04 -04:00
// Grab X root window if requested
2024-08-20 18:33:37 -04:00
if flags . SetXRootName {
var err error
2024-08-24 03:13:04 -04:00
x , root , err = grabXRootWindow ( )
2024-08-20 18:33:37 -04:00
if err != nil {
2024-08-24 03:13:04 -04:00
log . Fatalf ( "error grabbing X root window: %v\n" , err )
2024-08-20 18:33:37 -04:00
}
2024-08-24 03:13:04 -04:00
defer x . Close ( )
2024-08-20 18:33:37 -04:00
}
2024-08-14 16:19:18 -04:00
// Initialize modules
for i := range modules {
2024-08-24 03:13:04 -04:00
go modules [ i ] . Init ( i )
2024-08-14 12:28:29 -04:00
}
2024-08-24 03:13:04 -04:00
// Monitor changes to the combined output
go monitorUpdates ( flags . SetXRootName )
2024-08-14 16:19:18 -04:00
2024-08-24 03:13:04 -04:00
// Handle signals sent for modules
2024-08-14 16:19:18 -04:00
for sig := range sigChan {
2024-08-24 03:13:04 -04:00
go handleSignal ( sig )
2024-08-14 12:28:29 -04:00
}
2024-07-30 04:02:05 -04:00
}