M README.md => README.md +33 -29
@@ 242,27 242,20 @@ import (
type StaticText struct {
Text string `yaml:"textToDisplay"`
+ statusbar.BaseWidget
}
-func (w *StaticText) WidgetName() string { return "StaticText" }
-func (w *StaticText) New() statusbar.Widget { return new(StaticText) }
-
-func (w *StaticText) Run(ctx context.Context, update statusbar.UpdateFunc, done func()) {
- defer done()
- update([]statusbar.Block{
- {
- FullText: w.Text,
- Name: "static",
- },
- })
+func (w StaticText) Run(ctx context.Context, update statusbar.UpdateFunc) {
+ b := w.Block()
+ b.FullText = w.Text
+ b.Name = "static"
+ update([]statusbar.Block{b})
<-ctx.Done()
}
func main() {
- cfg := statusbar.Configuration{}
- cfg.RegisterWidget(new(StaticText))
- err := cfg.Run(nil)
- abortOnErr(err)
+ statusbar.RegisterWidget[StaticText]("StaticText")
+ abortOnErr(statusbar.Run(nil))
}
func abortOnErr(err error) {
@@ 287,26 280,37 @@ forget to update the swaybar configuration (as described in the
### Additional notes
-1. Widgets must implement the Widget interface as defined in the statusbar
- library. This means that they must provide three methods, as illustrated in
- the example above.
+1. Widgets must implement the Widget interface as defined by the statusbar
+ library. This means that they must provide the Run method, as illustrated
+ in the example above.
-2. Widgets should be structs with a pointer receiver. This is required by the
- configuration parser.
+### Additional notes
+
+1. Widgets must implement the Widget interface as defined by the statusbar
+ library. This means that they must provide the Run method, as shown in the
+ example above.
-3. The Run method is executed by a status bar in a dedicated goroutine. It is
- responsible for updating the contents of the status bar blocks. In the
- provided example, the update function is called only once, but usually it
+2. Widgets should be structs with a pointer receiver. They should also embed
+ the `BaseWidget` struct, as demonstrated above. This allows them to accept
+ default configurations and be registered using the
+ `statusbar.RegisterWidget` function.
+
+3. The `Run` method is executed by a status bar in a dedicated goroutine. It
+ is responsible for updating the contents of the status bar blocks. In the
+ provided example, the `update` function is called only once, but usually it
should be called within the loop whenever a status change is detected.
-4. In general, the Run method should continue running until the context is
+4. The `Block` method provided by the `BaseWidget` returns the block that has
+ been preinitialized with common configuration options.
+
+5. In general, the Run method should continue running until the context is
canceled. However, it may exit earlier if necessary. In such cases, the
- status bar will preserve the last version of the widget's blocks. In the
- provided example, the `<-ctx.Done()` statement could be omitted.
+ status bar will preserve the last version of the widget's blocks. To remove
+ the widget's blocks from the status bar, you can pass `nil` to the `update`
+ function.
-5. The Run method must call the done function just before it returns. To
- achieve this, it is suggested to use the `defer done()` statement as the first
- line of code in the method.
+6. Note that in the provided example, the `<-ctx.Done()` statement could be
+ omitted.
## Contributions
M configuration.go => configuration.go +13 -26
@@ 10,19 10,16 @@ import (
"path/filepath"
"github.com/adrg/xdg"
- "gobytes.dev/statusbar/internal"
"gopkg.in/yaml.v3"
)
var DefaultConfigFile = filepath.Join(xdg.ConfigHome, "statusbar", "config.yaml")
-type Constructor interface {
- WidgetName() string
- New() Widget
-}
+var globalConfiguration = Configuration{}
type Configuration struct {
- cons map[string]Constructor
+ cons map[string]constructor
+ common BaseWidget
widgets []Widget
logger *slog.Logger
nodef bool
@@ 30,13 27,6 @@ type Configuration struct {
func (c *Configuration) NoBuiltinWidgets() { c.nodef = true }
-func (c *Configuration) RegisterWidget(cons Constructor) {
- if c.widgets == nil {
- c.cons = make(map[string]Constructor)
- }
- c.cons[cons.WidgetName()] = cons
-}
-
func (c *Configuration) SetLogger(l *slog.Logger) { c.logger = l }
func (c *Configuration) ParseFile(filename string) error {
@@ 101,6 91,12 @@ func (c *Configuration) UnmarshalYAML(n *yaml.Node) error {
return nil
}
+func Run(args []string) error { return globalConfiguration.Run(args) }
+func NoBuiltinWidgets() { globalConfiguration.NoBuiltinWidgets() }
+func SetLogger(l *slog.Logger) { globalConfiguration.SetLogger(l) }
+func Widgets() []Widget { return globalConfiguration.Widgets() }
+func GlobalStatusBar() *StatusBar { return globalConfiguration.StatusBar() }
+
func (c *Configuration) parseWidgetConfig(n *yaml.Node) error {
rec := struct {
Widget string `yaml:"widget"`
@@ 112,7 108,7 @@ func (c *Configuration) parseWidgetConfig(n *yaml.Node) error {
if cons, ok := c.cons[rec.Widget]; !ok {
return fmt.Errorf("unknown widget: %s", rec.Widget)
} else {
- widget := cons.New()
+ widget := cons.Construct(c.common)
err := n.Decode(widget)
if err != nil {
return err
@@ 127,20 123,11 @@ func (c *Configuration) builtinWidgets() {
return
}
if c.cons == nil {
- c.cons = make(map[string]Constructor)
+ c.cons = make(map[string]constructor)
}
- for i := range builtinWidgets {
- name := builtinWidgets[i].WidgetName()
+ for name, constr := range registry {
if _, ok := c.cons[name]; !ok {
- c.cons[name] = builtinWidgets[i]
+ c.cons[name] = constr
}
}
}
-
-var builtinWidgets = []Constructor{}
-
-func init() {
- internal.Register = func(w any) {
- builtinWidgets = append(builtinWidgets, w.(Constructor))
- }
-}
M configuration_test.go => configuration_test.go +1 -1
@@ 20,7 20,7 @@ func TestConfiguration(t *testing.T) {
dateFormat: date-format
timeFormat: time-format
`
- c := statusbar.Configuration{}
+ c := statusbar.Registry{}
err := c.Parse(bytes.NewBufferString(config))
requireNoErr(t, err)
expWidgets := []statusbar.Widget{
A internal/mark.go => internal/mark.go +5 -0
@@ 0,0 1,5 @@
+package internal
+
+type Mark struct{}
+
+var M = Mark{}
D internal/registry.go => internal/registry.go +0 -3
@@ 1,3 0,0 @@
-package internal
-
-var Register func(any)
D internal/registry/reg.go => internal/registry/reg.go +0 -10
@@ 1,10 0,0 @@
-package registry
-
-import (
- "gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal"
-)
-
-func RegisterWidget(w statusbar.Constructor) {
- internal.Register(w)
-}
A registry.go => registry.go +42 -0
@@ 0,0 1,42 @@
+package statusbar
+
+import "gobytes.dev/statusbar/internal"
+
+type widget[T any] interface {
+ *T
+ setDefault(c BaseWidget)
+ Widget
+}
+
+func construct[T any, pT widget[T]](c BaseWidget) Widget {
+ var w pT = new(T)
+ w.setDefault(c)
+ return w
+}
+
+type constructor interface {
+ Construct(c BaseWidget) Widget
+}
+
+type constrFunc func(c BaseWidget) Widget
+
+func (co constrFunc) Construct(c BaseWidget) Widget {
+ return co(c)
+}
+
+func RegisterWidgetIn[T any, pT widget[T]](c *Configuration, name string) {
+ if c.cons == nil {
+ c.cons = make(map[string]constructor)
+ }
+ c.cons[name] = constrFunc(construct[T, pT])
+}
+
+var registry = make(map[string]constructor)
+
+func RegisterWidget[T any, pT widget[T]](name string) {
+ RegisterWidgetIn[T, pT](&globalConfiguration, name)
+}
+
+func RegisterInternalWidget[T any, pT widget[T]](m internal.Mark, name string) {
+ registry[name] = constrFunc(construct[T, pT])
+}
M statusbar.go => statusbar.go +9 -8
@@ 17,7 17,7 @@ import (
type UpdateFunc func([]Block)
type Widget interface {
- Run(ctx context.Context, fn UpdateFunc, done func())
+ Run(ctx context.Context, fn UpdateFunc)
}
type EventHandler interface {
@@ 28,7 28,7 @@ type StatusBar struct {
LogFile string
debug bool
errf io.WriteCloser
- widgets []*widget
+ widgets []*updater
index []int
blocks []Block
headerEmited bool
@@ 41,13 41,13 @@ type StatusBar struct {
func New(ws ...Widget) *StatusBar {
b := &StatusBar{
- widgets: make([]*widget, len(ws)),
+ widgets: make([]*updater, len(ws)),
index: make([]int, len(ws)),
sigs: make(chan os.Signal, 3),
enc: json.NewEncoder(os.Stdout),
}
for i, w := range ws {
- widget := &widget{instance: i, widget: w, bar: b}
+ widget := &updater{instance: i, widget: w, bar: b}
b.widgets[i] = widget
b.index[i] = 0
}
@@ 259,16 259,17 @@ func (b *StatusBar) signals(ctx context.Context, done func()) {
}
}
-type widget struct {
+type updater struct {
instance int
widget Widget
bar *StatusBar
}
-func (w *widget) update(blocks []Block) {
+func (w *updater) update(blocks []Block) {
w.bar.update(w.instance, blocks)
}
-func (w *widget) run(ctx context.Context, done func()) {
- w.widget.Run(ctx, w.update, done)
+func (w *updater) run(ctx context.Context, done func()) {
+ w.widget.Run(ctx, w.update)
+ done()
}
M swaybarproto.go => swaybarproto.go +2 -2
@@ 21,8 21,8 @@ type Block struct {
Align string `json:"align,omitempty"`
Name string `json:"name,omitempty"`
Instance string `json:"instance,omitempty"`
- Separator bool `json:"separator,omitempty"`
- SeparatorBlockWidth int `json:"separator_block_width,omitempty"`
+ Separator *bool `json:"separator,omitempty"`
+ SeparatorBlockWidth *int `json:"separator_block_width,omitempty"`
Markup string `json:"markup,omitempty"`
}
A widget.go => widget.go +36 -0
@@ 0,0 1,36 @@
+package statusbar
+
+type BaseWidget struct {
+ Border string `yaml:"border"`
+ BorderTop int `yaml:"borderTop"`
+ BorterBottom int `yaml:"borderBottom"`
+ BorderLeft int `yaml:"borderLeft"`
+ BorderRight int `yaml:"borderRight"`
+ MinWidth int `yaml:"minWidth"`
+ Separator *bool `yaml:"separator"`
+ SeparatorBlockWidth *int `yaml:"separatorBlockWidth"`
+}
+
+func (p *BaseWidget) setDefault(c BaseWidget) {
+ p.Border = c.Border
+ p.BorderTop = c.BorderTop
+ p.BorterBottom = c.BorterBottom
+ p.BorderLeft = c.BorderLeft
+ p.BorderRight = c.BorderRight
+ p.MinWidth = c.MinWidth
+ p.Separator = c.Separator
+ p.SeparatorBlockWidth = c.SeparatorBlockWidth
+}
+
+func (p *BaseWidget) Block() Block {
+ b := Block{}
+ b.Border = p.Border
+ b.BorderTop = p.BorderTop
+ b.BorterBottom = p.BorterBottom
+ b.BorderLeft = p.BorderLeft
+ b.BorderRight = p.BorderRight
+ b.MinWidth = p.MinWidth
+ b.Separator = p.Separator
+ b.SeparatorBlockWidth = p.SeparatorBlockWidth
+ return b
+}
M widgets/base.go => widgets/base.go +4 -0
@@ 3,11 3,15 @@ package widgets
import (
"fmt"
"log/slog"
+
+ "gobytes.dev/statusbar"
)
type baseWidget struct {
logger *slog.Logger
prefix string
+
+ statusbar.BaseWidget `yaml:",inline"`
}
func (p *baseWidget) format(format string, args ...any) string {
M widgets/batmon.go => widgets/batmon.go +5 -9
@@ 13,7 13,7 @@ import (
"github.com/godbus/dbus/v5"
"gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal/registry"
+ "gobytes.dev/statusbar/internal"
)
type notification struct {
@@ 64,15 64,11 @@ type BatteryMonitor struct {
chargingIcons []rune
dischargingIcons []rune
idx []int
- baseWidget
+ baseWidget `yaml:",inline"`
}
-func (w *BatteryMonitor) WidgetName() string { return "BatteryMonitor" }
-func (w *BatteryMonitor) New() statusbar.Widget { return new(BatteryMonitor) }
-
-func (w *BatteryMonitor) Run(ctx context.Context, update statusbar.UpdateFunc, done func()) {
- defer done()
- w.baseWidget.prefix = "BatteryMonitor"
+func (w *BatteryMonitor) Run(ctx context.Context, update statusbar.UpdateFunc) {
+ w.prefix = "BatteryMonitor"
w.init()
w.emitStatus(update)
t := time.NewTicker(1 * time.Second)
@@ 254,5 250,5 @@ func (w *BatteryMonitor) notify() {
}
func init() {
- registry.RegisterWidget(new(BatteryMonitor))
+ statusbar.RegisterInternalWidget[BatteryMonitor](internal.M, "BatteryMonitor")
}
M widgets/batmon_test.go => widgets/batmon_test.go +10 -0
@@ 3,6 3,8 @@ package widgets
import (
"strconv"
"testing"
+
+ "gopkg.in/yaml.v3"
)
func TestBatteryMonitor(t *testing.T) {
@@ 383,3 385,11 @@ func TestBatteryMonitor(t *testing.T) {
})
}
}
+
+func TestBatteryMonitorConfig(t *testing.T) {
+ cfg := "separator: true\nborderTop: 10"
+ w := BatteryMonitor{}
+ yaml.Unmarshal([]byte(cfg), &w)
+ o := opts{}
+ o.assertEqual(t, w.BorderTop, 10)
+}
M widgets/date.go => widgets/date.go +5 -7
@@ 5,20 5,18 @@ import (
"time"
"gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal/registry"
+ "gobytes.dev/statusbar/internal"
)
type DateTime struct {
Format string `yaml:"format"`
TimeFormat string `yaml:"timeFormat"`
DateFormat string `yaml:"dateFormat"`
-}
-func (w *DateTime) WidgetName() string { return "DateTime" }
-func (w *DateTime) New() statusbar.Widget { return new(DateTime) }
+ baseWidget
+}
-func (w *DateTime) Run(ctx context.Context, fn statusbar.UpdateFunc, done func()) {
- defer done()
+func (w *DateTime) Run(ctx context.Context, fn statusbar.UpdateFunc) {
if w.Format == "" && w.DateFormat == "" && w.TimeFormat == "" {
w.Format = "2006-01-02 15:04:05"
}
@@ 68,5 66,5 @@ func (w *DateTime) publish(update statusbar.UpdateFunc, now time.Time) {
}
func init() {
- registry.RegisterWidget(new(DateTime))
+ statusbar.RegisterInternalWidget[DateTime](internal.M, "DateTime")
}
M widgets/external.go => widgets/external.go +3 -7
@@ 14,7 14,7 @@ import (
"syscall"
"gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal/registry"
+ "gobytes.dev/statusbar/internal"
)
type ExternalProgram struct {
@@ 35,11 35,7 @@ type ExternalProgram struct {
baseWidget
}
-func (p *ExternalProgram) WidgetName() string { return "ExternalProgram" }
-func (w *ExternalProgram) New() statusbar.Widget { return new(ExternalProgram) }
-
-func (p *ExternalProgram) Run(ctx context.Context, update statusbar.UpdateFunc, done func()) {
- defer done()
+func (p *ExternalProgram) Run(ctx context.Context, update statusbar.UpdateFunc) {
p.baseWidget.prefix = fmt.Sprintf("external %s", p.Command)
p.sigs = make(chan os.Signal, 2)
var err error
@@ 169,5 165,5 @@ func (p *ExternalProgram) signals(ctx context.Context, done func()) {
}
func init() {
- registry.RegisterWidget(new(ExternalProgram))
+ statusbar.RegisterInternalWidget[ExternalProgram](internal.M, "ExternalProgram")
}
D widgets/init.go => widgets/init.go +0 -1
@@ 1,1 0,0 @@
-package widgets
M widgets/sysmon.go => widgets/sysmon.go +14 -20
@@ 8,20 8,19 @@ import (
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal/registry"
+ "gobytes.dev/statusbar/internal"
)
type SystemMonitor struct {
Format string `yaml:"format"`
- format Format
- update statusbar.UpdateFunc
- idx []int
- baseWidget
+ format Format
+ update statusbar.UpdateFunc
+ idx []int
+ baseWidget `yaml:",inline"`
}
-func (w *SystemMonitor) Run(ctx context.Context, b statusbar.UpdateFunc, done func()) {
- defer done()
+func (w *SystemMonitor) Run(ctx context.Context, b statusbar.UpdateFunc) {
w.prefix = "SysMon"
w.update = b
if w.Format == "" {
@@ 40,9 39,6 @@ func (w *SystemMonitor) Run(ctx context.Context, b statusbar.UpdateFunc, done fu
}
}
-func (w *SystemMonitor) WidgetName() string { return "SystemMonitor" }
-func (w *SystemMonitor) New() statusbar.Widget { return new(SystemMonitor) }
-
func (w *SystemMonitor) emitStatus(ctx context.Context) {
cpu, err := cpu.PercentWithContext(ctx, 0, false)
if err != nil {
@@ 60,17 56,15 @@ func (w *SystemMonitor) emitStatus(ctx context.Context) {
return
}
status := w.format.Format(cpu[0], mem.UsedPercent, load.Load1)
- w.update([]statusbar.Block{
- {
- FullText: status,
- Align: "left",
- Name: "sysmon",
- Instance: "0",
- Markup: "pango",
- },
- })
+ b := w.Block()
+ b.FullText = status
+ b.Align = "left"
+ b.Name = "sysmon"
+ b.Instance = "0"
+ b.Markup = "pango"
+ w.update([]statusbar.Block{b})
}
func init() {
- registry.RegisterWidget(new(SystemMonitor))
+ statusbar.RegisterInternalWidget[SystemMonitor](internal.M, "SystemMonitor")
}
M widgets/temp.go => widgets/temp.go +3 -7
@@ 9,7 9,7 @@ import (
"time"
"gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal/registry"
+ "gobytes.dev/statusbar/internal"
)
type TemperatureMonitor struct {
@@ 22,11 22,7 @@ type TemperatureMonitor struct {
baseWidget
}
-func (w *TemperatureMonitor) WidgetName() string { return "TempMonitor" }
-func (w *TemperatureMonitor) New() statusbar.Widget { return new(TemperatureMonitor) }
-
-func (w *TemperatureMonitor) Run(ctx context.Context, update statusbar.UpdateFunc, done func()) {
- defer done()
+func (w *TemperatureMonitor) Run(ctx context.Context, update statusbar.UpdateFunc) {
w.init()
w.status = make([]statusbar.Block, 0, 2)
w.emitStatus(update)
@@ 91,5 87,5 @@ func (w *TemperatureMonitor) init() {
}
func init() {
- registry.RegisterWidget(new(TemperatureMonitor))
+ statusbar.RegisterInternalWidget[TemperatureMonitor](internal.M, "TempMonitor")
}
M widgets/volume.go => widgets/volume.go +3 -7
@@ 11,7 11,7 @@ import (
"github.com/google/shlex"
"gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal/registry"
+ "gobytes.dev/statusbar/internal"
)
type Volume struct {
@@ 38,11 38,7 @@ type Volume struct {
baseWidget
}
-func (w *Volume) WidgetName() string { return "Volume" }
-func (w *Volume) New() statusbar.Widget { return new(Volume) }
-
-func (w *Volume) Run(ctx context.Context, update statusbar.UpdateFunc, done func()) {
- defer done()
+func (w *Volume) Run(ctx context.Context, update statusbar.UpdateFunc) {
w.init()
if w.volMonCmd == nil {
w.errorf("No volMonCommand provided")
@@ 185,5 181,5 @@ func (w *Volume) init() {
}
func init() {
- registry.RegisterWidget(new(Volume))
+ statusbar.RegisterInternalWidget[Volume](internal.M, "Volume")
}
M widgets/window.go => widgets/window.go +3 -7
@@ 7,7 7,7 @@ import (
"unicode"
"gobytes.dev/statusbar"
- "gobytes.dev/statusbar/internal/registry"
+ "gobytes.dev/statusbar/internal"
"gobytes.dev/swayipc"
)
@@ 24,11 24,7 @@ type Window struct {
baseWidget
}
-func (w *Window) WidgetName() string { return "Window" }
-func (w *Window) New() statusbar.Widget { return new(Window) }
-
-func (w *Window) Run(ctx context.Context, update statusbar.UpdateFunc, done func()) {
- defer done()
+func (w *Window) Run(ctx context.Context, update statusbar.UpdateFunc) {
w.init()
w.update = update
w.events = make(chan *swayipc.WindowEvent)
@@ 218,5 214,5 @@ func (w *Window) init() {
}
func init() {
- registry.RegisterWidget(new(Window))
+ statusbar.RegisterInternalWidget[Window](internal.M, "Window")
}