Add variable-period block refreshes

Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
akp 2023-01-16 03:35:54 +00:00
parent 5c7c87d911
commit ec663d15d1
No known key found for this signature in database
GPG key ID: AA5726202C8879B7
14 changed files with 140 additions and 64 deletions

View file

@ -33,7 +33,7 @@ func main() {
}
func run() error {
b := i3bar.New(os.Stdout, os.Stdin, time.Second*2, syscall.SIGUSR1)
b := i3bar.New(os.Stdout, os.Stdin, syscall.SIGUSR1)
if err := b.Initialise(); err != nil {
return err
}
@ -57,8 +57,8 @@ func run() error {
providers.NewAudioPlayer(32),
)
if err := b.Emit([]i3bar.BlockGenerator{
providers.NewPlainText("cdmbar" + commitHash),
if err := b.Emit([]*i3bar.Block{
{FullText: "cdmbar" + commitHash},
}); err != nil {
return err
}

View file

@ -14,23 +14,25 @@ import (
"github.com/rs/zerolog/log"
)
type I3bar struct {
writer io.Writer
reader io.Reader
updateInterval time.Duration
updateSignal syscall.Signal
registeredGenerators []BlockGenerator
registeredConsumers []ClickEventConsumer
hasInitialised bool
hasSentFirstLine bool
type generatorInfo struct {
Provider BlockGenerator
HasClickConsumer bool
Last *Block
}
func New(writer io.Writer, reader io.Reader, updateInterval time.Duration, updateSignal syscall.Signal) *I3bar {
type I3bar struct {
writer io.Writer
reader io.Reader
updateSignal syscall.Signal
generators []*generatorInfo
tickNumber uint8
}
func New(writer io.Writer, reader io.Reader, updateSignal syscall.Signal) *I3bar {
return &I3bar{
writer: writer,
reader: reader,
updateInterval: updateInterval,
updateSignal: updateSignal,
}
}
@ -44,11 +46,10 @@ func (b *I3bar) Initialise() error {
return err
}
if _, err := b.writer.Write(append(capabilities, '\n')); err != nil {
if _, err := b.writer.Write(append(capabilities, []byte("\n[\n")...)); err != nil {
return err
}
b.hasInitialised = true
return nil
}
@ -59,42 +60,13 @@ var defaultColorSet = &ColorSet{
Background: &Color{0x28, 0x28, 0x28},
}
func (b *I3bar) Emit(generators []BlockGenerator) error {
if !b.hasInitialised {
if err := b.Initialise(); err != nil {
return err
}
}
var blocks []*Block
for _, generator := range generators {
b, err := generator.Block(defaultColorSet)
if err != nil {
log.Error().Err(err).Str("generator", fmt.Sprintf("%T", generator)).Send()
b = &Block{
FullText: "ERROR",
TextColor: defaultColorSet.Bad,
}
}
if b == nil {
continue
}
blocks = append(blocks, b)
}
func (b *I3bar) Emit(blocks []*Block) error {
jsonData, err := json.Marshal(blocks)
if err != nil {
return err
}
if !b.hasSentFirstLine {
jsonData = append([]byte("[\n"), jsonData...)
b.hasSentFirstLine = true
} else {
jsonData = append([]byte{','}, jsonData...)
}
jsonData = append(jsonData, '\n')
jsonData = append(jsonData, []byte(",\n")...)
if _, err := b.writer.Write(jsonData); err != nil {
return err
@ -107,24 +79,25 @@ func (b *I3bar) Emit(generators []BlockGenerator) error {
// function should not be called after StartLoop is called.
func (b *I3bar) RegisterBlockGenerator(bg ...BlockGenerator) {
for _, bgx := range bg {
b.registeredGenerators = append([]BlockGenerator{bgx}, b.registeredGenerators...)
}
_, hasClickConsumer := bgx.(ClickEventConsumer)
for _, generator := range bg {
if g, ok := generator.(ClickEventConsumer); ok {
b.registeredConsumers = append(b.registeredConsumers, g)
}
metadata := new(generatorInfo)
metadata.Provider = bgx
metadata.HasClickConsumer = hasClickConsumer
b.generators = append([]*generatorInfo{metadata}, b.generators...)
}
}
func (b *I3bar) StartLoop() error {
// The ticker will start after the specified duration, not when we
// instantiate it. Circumventing that here by calling Emit now.
if err := b.Emit(b.registeredGenerators); err != nil {
if err := b.tick(false); err != nil {
return err
}
ticker := time.NewTicker(b.updateInterval)
ticker := time.NewTicker(time.Second)
sigUpdate := make(chan os.Signal, 1)
signal.Notify(sigUpdate, os.Signal(b.updateSignal))
@ -135,17 +108,58 @@ func (b *I3bar) StartLoop() error {
for {
select {
case <-sigUpdate:
if err := b.Emit(b.registeredGenerators); err != nil {
log.Error().Err(err).Msg("could not emit registered generators")
if err := b.tick(true); err != nil {
log.Error().Err(err).Msg("could not tick")
}
case <-ticker.C:
if err := b.Emit(b.registeredGenerators); err != nil {
log.Error().Err(err).Msg("could not emit registered generators")
if err := b.tick(false); err != nil {
log.Error().Err(err).Msg("could not tick")
}
}
}
}
func (b *I3bar) tick(override bool) error {
var hasChanged bool
for _, gen := range b.generators {
if override || (gen.Provider.Frequency() == 0 && gen.Last == nil) || (gen.Provider.Frequency() != 0 && b.tickNumber%gen.Provider.Frequency() == 0) {
block, err := gen.Provider.Block(defaultColorSet)
if err != nil {
log.Error().Err(err).Str("generator", fmt.Sprintf("%T", gen.Provider)).Send()
block = &Block{
FullText: "ERROR",
TextColor: defaultColorSet.Bad,
}
}
if block == nil {
block = &Block{
FullText: "MISSING",
TextColor: defaultColorSet.Warning,
}
}
if block != gen.Last {
gen.Last = block
hasChanged = true
}
}
}
if hasChanged {
var blocks []*Block
for _, gen := range b.generators {
blocks = append(blocks, gen.Last)
}
if err := b.Emit(blocks); err != nil {
return err
}
}
b.tickNumber += 1
return nil
}
func (b *I3bar) consumerLoop(requestBarRefresh func()) {
r := bufio.NewReader(b.reader)
for {
@ -166,10 +180,13 @@ func (b *I3bar) consumerLoop(requestBarRefresh func()) {
continue // idk what this could be but it's not relevant so BYE!
}
for _, consumer := range b.registeredConsumers {
consumerName, consumerInstance := consumer.GetNameAndInstance()
for _, consumer := range b.generators {
if !consumer.HasClickConsumer {
continue
}
consumerName, consumerInstance := consumer.Provider.GetNameAndInstance()
if consumerName == event.Name && (consumerName == "" || consumerInstance == event.Instance) {
if consumer.OnClick(event) {
if consumer.Provider.(ClickEventConsumer).OnClick(event) {
requestBarRefresh()
}
}
@ -204,6 +221,9 @@ type ProvidesNameAndInstance interface {
type BlockGenerator interface {
ProvidesNameAndInstance
Block(*ColorSet) (*Block, error)
// Frequency should return the frequency at which the block is updated, as
// "1 per n seconds".
Frequency() uint8
}
type ClickEvent struct {

View file

@ -38,6 +38,10 @@ func NewAudioPlayer(maxLabelLength int) *AudioPlayer {
}
}
func (g *AudioPlayer) Frequency() uint8 {
return 1
}
type playingAudioInfo struct {
Track string
Artist string

View file

@ -27,6 +27,7 @@ type Battery struct {
name string
previousWasBackgroundWarning bool
isAlert bool
}
func NewBattery(deviceName string, fullThreshold, okThreshold, warningThreshold float32) i3bar.BlockGenerator {
@ -35,10 +36,17 @@ func NewBattery(deviceName string, fullThreshold, okThreshold, warningThreshold
FullThreshold: fullThreshold,
OkThreshold: okThreshold,
WarningThreshold: warningThreshold,
name: "battery",
name: "battery",
}
}
func (g *Battery) Frequency() uint8 {
if g.isAlert {
return 1
}
return 5
}
func (g *Battery) infoPath() string {
return path.Join("/sys/class/power_supply", g.DeviceName)
}
@ -124,6 +132,8 @@ func (g *Battery) Block(colors *i3bar.ColorSet) (*i3bar.Block, error) {
if percentage < g.WarningThreshold && g.WarningThreshold != 0 {
g.isAlert = true
if g.previousWasBackgroundWarning || state == batteryStateCharging { // disable flashing when on charge
block.TextColor = colors.Bad
} else {
@ -134,6 +144,8 @@ func (g *Battery) Block(colors *i3bar.ColorSet) (*i3bar.Block, error) {
} else if percentage < g.OkThreshold && g.OkThreshold != 0 {
block.TextColor = colors.Warning
} else {
g.isAlert = false
}
switch state {

View file

@ -30,6 +30,10 @@ func NewCPU(okThreshold, warningThreshold float32) i3bar.BlockGenerator {
return m
}
func (g *CPU) Frequency() uint8 {
return 2
}
func (g *CPU) doSample() error {
contents, err := ioutil.ReadFile("/proc/stat")
if err != nil {

View file

@ -17,6 +17,10 @@ func NewDateTime() i3bar.BlockGenerator {
}
}
func (g *DateTime) Frequency() uint8 {
return 1
}
func (g *DateTime) Block(*i3bar.ColorSet) (*i3bar.Block, error) {
cTime := time.Now().Local()

View file

@ -27,6 +27,10 @@ func NewDisk(mountPath string, okThreshold, warningThreshold float32) i3bar.Bloc
}
}
func (g *Disk) Frequency() uint8 {
return 5
}
func (g *Disk) getAvailable() (float32, error) {
cmdout, err := runCommand("df", "-x", "fuse.portal")
if err != nil {

View file

@ -21,6 +21,10 @@ func NewIPAddress(adapter string) i3bar.BlockGenerator {
}
}
func (g *IPAddress) Frequency() uint8 {
return 5
}
func (g *IPAddress) getAdapterIPAddress() (string, error) {
// call ifconfig
output, err := runCommand("ifconfig")

View file

@ -21,6 +21,10 @@ func NewLaunchProgram(text string, executable string) i3bar.BlockGenerator {
}
}
func (g *LaunchProgram) Frequency() uint8 {
return 255
}
func (g *LaunchProgram) Block(*i3bar.ColorSet) (*i3bar.Block, error) {
return &i3bar.Block{
Name: g.name,

View file

@ -25,6 +25,10 @@ func NewMemory(okThreshold, warningThreshold float32) i3bar.BlockGenerator {
}
}
func (g *Memory) Frequency() uint8 {
return 2
}
var (
memoryTotalRegexp = regexp.MustCompile(`MemTotal: +(\d+) kB`)
memoryAvailableRegexp = regexp.MustCompile(`MemAvailable: +(\d+) kB`)

View file

@ -15,6 +15,10 @@ func NewPlainText(text string) i3bar.BlockGenerator {
}
}
func (g *PlainText) Frequency() uint8 {
return 255
}
func (g *PlainText) Block(*i3bar.ColorSet) (*i3bar.Block, error) {
return &i3bar.Block{
Name: g.name,

View file

@ -25,6 +25,10 @@ func NewPulseaudioVolume() i3bar.BlockGenerator {
}
}
func (g *PulseaudioVolume) Frequency() uint8 {
return 2
}
func (g *PulseaudioVolume) getInfo() (string, error) {
x, err := runCommand("pacmd", "info")
return string(x), err

View file

@ -28,6 +28,10 @@ func NewTimer(useShortLabel bool) i3bar.BlockGenerator {
}
}
func (g *Timer) Frequency() uint8 {
return 1
}
func (g *Timer) OnClick(event *i3bar.ClickEvent) bool {
resetButtonPressed := event.Button == i3bar.RightMouseButton
triggerButtonPressed := event.Button == i3bar.LeftMouseButton

View file

@ -26,6 +26,10 @@ func NewWiFi(adapter string, okThreshold float32) i3bar.BlockGenerator {
}
}
func (g *WiFi) Frequency() uint8 {
return 5
}
var (
// For use with iwconfig
essidRegexp = regexp.MustCompile(`ESSID:(?:"(.+)"|off/any)`)