From ba7fc174d8f2d2f326370e07a915f077f0ed0728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kintzi?= Date: Mon, 13 Nov 2023 13:34:19 +0100 Subject: [PATCH] Add common options to each widget. --- README.md | 47 +++++++++++++++++-- configuration.go | 7 ++- configuration_test.go | 19 +++++--- swaybarproto.go | 106 +++++++++++++++++++++++++++++++++++------- swaybarproto_test.go | 39 ++++++++++++++++ widget.go | 27 +++++++---- widgets/batmon.go | 20 ++++---- widgets/date.go | 43 ++++------------- widgets/sysmon.go | 1 - widgets/temp.go | 21 ++++----- widgets/volume.go | 17 +++---- widgets/window.go | 18 ++++--- 12 files changed, 250 insertions(+), 115 deletions(-) create mode 100644 swaybarproto_test.go diff --git a/README.md b/README.md index ccffcc5..cba1f70 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ By default, the program reads its configuration from the for further customization: ``` +- widget: DefaultConfig - widget: Window excludeAppIDs: ["foot"] titlePrefix: "\uf2a3 " @@ -27,9 +28,11 @@ for further customization: - widget: ExternalProgram command: /usr/bin/i3status - widget: SystemMonitor - format: "\ue8b0 {cpu}% \ue8b2 {mem}% \ue8b3 {load}%" + format: "\ue8b0 {cpu}% \ue8b2 {mem}% \ue8b3 {load}%" + separator: false + separatorBlockWidth: 0 - widget: TempMonitor - format: "\ue8a2 {temp}\u00B0C \ue8a1 {fan}" + format: " \ue8a2 {temp}\u00B0C \ue8a1 {fan}" - widget: Volume scrollDownCommand: volumeup scrollUpCommand: volumedown @@ -65,6 +68,13 @@ bar { Currently, the statusbar provides three widgets, and more will be added soon. +### DefaultConfig + +The DefaultConfig is not a real widget. Instead, all the configuration options +given in this section of the config file are used to initialize widgets. +In the example above, no options were given, but you users can use each option +described in the "Common options" section below. + ### BatteryMonitor The BatteryMonitor widget is used to display the status and level of the @@ -209,7 +219,7 @@ device. It offers the following options for customization: The window widget displays the title of the focused window. It provides the following options: -- `titlePrefix` - a string that is added before the title (default: "\uf2a3 ") +- `titlePrefix` - a string that is added before the title (default: "") - `maxTitleLength` - if the length of the title exceeds the specified value, it is trimmed and an ellipsis symbol is added (default: 80, set to -1 to disable trimming) @@ -221,6 +231,37 @@ following options: This option allows you to specify a list of application IDs that will not be appended. +### Common options + +Options described in this section can be set for every widget, as well +as in the special widget DefaultConfig (described above). This options are +also described in the [swaybar-protocol(7)](https://man.archlinux.org/man/swaybar-protocol.7). + +- `color` - Sets the text color of the widget using #rrggbbaa or #rrggbb + notation. +- `background` - Sets the background color of the widget. +- `border` - Sets the border color for the widget. +- `borderTop` - Sets the height in pixels of the top border. The default value + is 1. +- `borderBottom` - Sets the height in pixels of the bottom border. The default + value is 1. +- `borderLeft` - Sets the width in pixels of the left border. The default + value is 1. +- `borderRight` - Sets the width in pixels of the right border. The default + value is 1. +- `minWidth` - Sets the minimum width to use for the widget. The width can be + specified in pixels or as a string to allow for dynamic calculation based on + the width of the text. +- `separator` - Specifies whether the bar separator should be drawn after the + widget. Refer to [sway-bar(5)](https://man.archlinux.org/man/sway-bar.5) for + more information on how to set the separator text. +- `separatorBlockWidth` - Sets the amount of pixels to leave blank after the + block. The separator text will be displayed centered in this gap. The + default value is 9 pixels. +- `align` - Specifies how the text should be aligned inside the block if it + does not span the full width. The options are left (default), right, or + center. + ## statusbar - The Library If there is a widget that you believe is missing from the project, you can diff --git a/configuration.go b/configuration.go index 9d25979..d9e30dc 100644 --- a/configuration.go +++ b/configuration.go @@ -105,7 +105,12 @@ func (c *Configuration) parseWidgetConfig(n *yaml.Node) error { if err != nil { return err } - if cons, ok := c.cons[rec.Widget]; !ok { + if rec.Widget == "DefaultConfig" { + err := n.Decode(&c.common) + if err != nil { + return err + } + } else if cons, ok := c.cons[rec.Widget]; !ok { return fmt.Errorf("unknown widget: %s", rec.Widget) } else { widget := cons.Construct(c.common) diff --git a/configuration_test.go b/configuration_test.go index f4f20f8..c151787 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -4,7 +4,6 @@ import ( "bytes" "testing" - "github.com/google/go-cmp/cmp" "gobytes.dev/statusbar" "gobytes.dev/statusbar/widgets" ) @@ -13,23 +12,29 @@ func cmpExternalProgram(w1, w2 *widgets.ExternalProgram) bool { return w1.Command == w2.Command } +func cmpDateTime(w1, w2 *widgets.DateTime) bool { + return w1 == w2 +} + func TestConfiguration(t *testing.T) { config := `- widget: ExternalProgram command: some-command - widget: DateTime - dateFormat: date-format - timeFormat: time-format + format: format ` - c := statusbar.Registry{} + c := statusbar.Configuration{} err := c.Parse(bytes.NewBufferString(config)) requireNoErr(t, err) expWidgets := []statusbar.Widget{ &widgets.ExternalProgram{Command: "some-command"}, - &widgets.DateTime{DateFormat: "date-format", TimeFormat: "time-format"}, + &widgets.DateTime{Format: "format"}, } ws := c.Widgets() - if !cmp.Equal(ws, expWidgets, cmp.Comparer(cmpExternalProgram)) { - t.Fatalf(cmp.Diff(ws, expWidgets)) + if ws[0].(*widgets.ExternalProgram).Command != expWidgets[0].(*widgets.ExternalProgram).Command { + t.Errorf("Unexpected widget: %+v", ws[0]) + } + if ws[1].(*widgets.DateTime).Format != expWidgets[1].(*widgets.DateTime).Format { + t.Errorf("Unexpected widget: %+v", ws[1]) } } diff --git a/swaybarproto.go b/swaybarproto.go index 31b4912..f8f02d4 100644 --- a/swaybarproto.go +++ b/swaybarproto.go @@ -1,5 +1,12 @@ package statusbar +import ( + "encoding/json" + "strconv" + + "gopkg.in/yaml.v3" +) + type Header struct { Version int `json:"version"` ClickEvents bool `json:"click_events"` @@ -8,24 +15,39 @@ type Header struct { } type Block struct { - FullText string `json:"full_text,omitempty"` - ShortText string `json:"short_text,omitempty"` - Color string `json:"color,omitempty"` - Background string `json:"background,omitempty"` - Border string `json:"border,omitempty"` - BorderTop int `json:"border_top,omitempty"` - BorterBottom int `json:"border_bottom,omitempty"` - BorderLeft int `json:"border_left,omitempty"` - BorderRight int `json:"border_right,omitempty"` - MinWidth int `json:"min_width,omitempty"` - 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"` - Markup string `json:"markup,omitempty"` + FullText string `json:"full_text,omitempty"` + ShortText string `json:"short_text,omitempty"` + Color string `json:"color,omitempty"` + Background string `json:"background,omitempty"` + Border string `json:"border,omitempty"` + BorderTop *int `json:"border_top,omitempty"` + BorderBottom *int `json:"border_bottom,omitempty"` + BorderLeft *int `json:"border_left,omitempty"` + BorderRight *int `json:"border_right,omitempty"` + MinWidth *MinWidth `json:"min_width,omitempty"` + 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"` + Markup string `json:"markup,omitempty"` } +func (b *Block) SetBorder(top, right, bottom, left int) { + b.BorderTop = &top + b.BorderBottom = &bottom + b.BorderRight = &left + b.BorderRight = &right +} + +func (b *Block) SetSeparator(show bool, width int) { + b.Separator = &show + b.SeparatorBlockWidth = &width +} + +func (b *Block) SetMinWidthInt(i int) { b.MinWidth = &MinWidth{i: i} } +func (b *Block) SetMinWidthString(s string) { b.MinWidth = &MinWidth{s: s} } + type Event struct { Name string `json:"name"` Instance string `json:"instance"` @@ -38,3 +60,55 @@ type Event struct { Width int `json:"width"` Height int `json:"height"` } + +type MinWidth struct { + s string + i int +} + +func (w *MinWidth) UnmarshalJSON(bs []byte) error { + mw := MinWidth{} + if bs[0] == '"' { + err := json.Unmarshal(bs, &mw.s) + if err != nil { + return err + } + } else { + err := json.Unmarshal(bs, &mw.i) + if err != nil { + return err + } + } + *w = mw + return nil +} + +func (w MinWidth) MarshalJSON() ([]byte, error) { + if w.s != "" { + return json.Marshal(w.s) + } else { + return json.Marshal(w.i) + } +} + +func (w *MinWidth) UnmarshalYAML(n *yaml.Node) error { + mw := MinWidth{} + err := n.Decode(&mw.s) + if err != nil { + return err + } + if i, err := strconv.Atoi(mw.s); err == nil { + mw.s = "" + mw.i = i + } + *w = mw + return err +} + +func (w MinWidth) MarshalYAML() (interface{}, error) { + if w.s != "" { + return w.s, nil + } else { + return w.i, nil + } +} diff --git a/swaybarproto_test.go b/swaybarproto_test.go new file mode 100644 index 0000000..5172343 --- /dev/null +++ b/swaybarproto_test.go @@ -0,0 +1,39 @@ +package statusbar + +import ( + "encoding/json" + "testing" +) + +func TestBlockUnMarshaling(t *testing.T) { + var ( + jsonStr = `[{"min_width":10},{"min_width":"10"},{"border":"color"}]` + blocks []Block + exp = []Block{ + {MinWidth: &MinWidth{i: 10}}, + {MinWidth: &MinWidth{s: "10"}}, + {Border: "color"}, + } + ) + err := json.Unmarshal([]byte(jsonStr), &blocks) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + if len(blocks) != len(exp) { + t.Errorf("Unexpected block deserialized from json: %+v", blocks) + } + for i := range exp { + if blocks[i].MinWidth == nil && exp[i].MinWidth != nil { + t.Errorf("Unexpected %d-th block deserialized from json: %+v", i, blocks[i]) + } else if blocks[i].MinWidth != nil && exp[i].MinWidth == nil { + t.Errorf("Unexpected %d-th block deserialized from json: %+v", i, blocks[i]) + } else if blocks[i].MinWidth != nil && *blocks[i].MinWidth != *exp[i].MinWidth { + t.Errorf("Unexpected %d-th block deserialized from json: %+v", i, blocks[i]) + } + blocks[i].MinWidth = nil + exp[i].MinWidth = nil + if blocks[i] != exp[i] { + t.Errorf("Unexpected %d-th block deserialized from json: %+v", i, blocks[i]) + } + } +} diff --git a/widget.go b/widget.go index 5090110..c1cb9b5 100644 --- a/widget.go +++ b/widget.go @@ -1,17 +1,22 @@ 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"` + Color string `yaml:"color,omitempty"` + Background string `yaml:"background,omitempty"` + Border string `yaml:"border"` + BorderTop *int `yaml:"borderTop"` + BorterBottom *int `yaml:"borderBottom"` + BorderLeft *int `yaml:"borderLeft"` + BorderRight *int `yaml:"borderRight"` + MinWidth *MinWidth `yaml:"minWidth"` + Separator *bool `yaml:"separator"` + SeparatorBlockWidth *int `yaml:"separatorBlockWidth"` + Align string `yaml:"align,omitempty"` } func (p *BaseWidget) setDefault(c BaseWidget) { + p.Color = c.Color + p.Background = c.Background p.Border = c.Border p.BorderTop = c.BorderTop p.BorterBottom = c.BorterBottom @@ -20,17 +25,21 @@ func (p *BaseWidget) setDefault(c BaseWidget) { p.MinWidth = c.MinWidth p.Separator = c.Separator p.SeparatorBlockWidth = c.SeparatorBlockWidth + p.Align = c.Align } func (p *BaseWidget) Block() Block { b := Block{} + b.Color = p.Color + b.Background = p.Background b.Border = p.Border b.BorderTop = p.BorderTop - b.BorterBottom = p.BorterBottom + b.BorderBottom = p.BorterBottom b.BorderLeft = p.BorderLeft b.BorderRight = p.BorderRight b.MinWidth = p.MinWidth b.Separator = p.Separator b.SeparatorBlockWidth = p.SeparatorBlockWidth + b.Align = p.Align return b } diff --git a/widgets/batmon.go b/widgets/batmon.go index 1a9d564..ec33ce7 100644 --- a/widgets/batmon.go +++ b/widgets/batmon.go @@ -64,7 +64,8 @@ type BatteryMonitor struct { chargingIcons []rune dischargingIcons []rune idx []int - baseWidget `yaml:",inline"` + + baseWidget `yaml:",inline"` } func (w *BatteryMonitor) Run(ctx context.Context, update statusbar.UpdateFunc) { @@ -123,16 +124,13 @@ func (w *BatteryMonitor) emitStatus(update statusbar.UpdateFunc) { } percent := chargeNow * 100 / chargeFull fullText := w.sprintf(status, percent) - update([]statusbar.Block{ - { - FullText: fullText, - Align: "left", - Name: "batmon", - Instance: "0", - Color: w.colors.Get(float64(percent)), - Markup: "pango", - }, - }) + bl := w.Block() + bl.FullText = fullText + bl.Name = "batmon" + bl.Instance = "0" + bl.Color = w.colors.Get(float64(percent)) + bl.Markup = "pango" + update([]statusbar.Block{bl}) if status == "Discharging" && percent < w.CriticalLevel && !w.wasNotified { w.notify() w.wasNotified = true diff --git a/widgets/date.go b/widgets/date.go index 1f5084a..d38cb31 100644 --- a/widgets/date.go +++ b/widgets/date.go @@ -9,15 +9,13 @@ import ( ) type DateTime struct { - Format string `yaml:"format"` - TimeFormat string `yaml:"timeFormat"` - DateFormat string `yaml:"dateFormat"` + Format string `yaml:"format"` - baseWidget + baseWidget `yaml:",inline"` } func (w *DateTime) Run(ctx context.Context, fn statusbar.UpdateFunc) { - if w.Format == "" && w.DateFormat == "" && w.TimeFormat == "" { + if w.Format == "" { w.Format = "2006-01-02 15:04:05" } t := time.NewTicker(1 * time.Second) @@ -34,35 +32,12 @@ func (w *DateTime) Run(ctx context.Context, fn statusbar.UpdateFunc) { func (w *DateTime) publish(update statusbar.UpdateFunc, now time.Time) { now = now.In(time.Local) - blocks := make([]statusbar.Block, 0, 3) - if w.Format != "" { - blocks = append(blocks, statusbar.Block{ - FullText: now.Format(w.Format), - Align: "left", - Name: "datetime", - Instance: "0", - Markup: "pango", - }) - } - if w.DateFormat != "" { - blocks = append(blocks, statusbar.Block{ - FullText: now.Format(w.DateFormat), - Align: "left", - Name: "date", - Instance: "0", - Markup: "pango", - }) - } - if w.TimeFormat != "" { - blocks = append(blocks, statusbar.Block{ - FullText: now.Format(w.TimeFormat), - Align: "left", - Name: "time", - Instance: "0", - Markup: "pango", - }) - } - update(blocks) + b := w.Block() + b.FullText = now.Format(w.Format) + b.Name = "datetime" + b.Instance = "0" + b.Markup = "pango" + update([]statusbar.Block{b}) } func init() { diff --git a/widgets/sysmon.go b/widgets/sysmon.go index 241bf65..85b6a15 100644 --- a/widgets/sysmon.go +++ b/widgets/sysmon.go @@ -58,7 +58,6 @@ func (w *SystemMonitor) emitStatus(ctx context.Context) { status := w.format.Format(cpu[0], mem.UsedPercent, load.Load1) b := w.Block() b.FullText = status - b.Align = "left" b.Name = "sysmon" b.Instance = "0" b.Markup = "pango" diff --git a/widgets/temp.go b/widgets/temp.go index 099edae..ecdf763 100644 --- a/widgets/temp.go +++ b/widgets/temp.go @@ -17,14 +17,12 @@ type TemperatureMonitor struct { TempInput string `yaml:"tempInput"` FanInput string `yaml:"fanInput"` - format Format - status []statusbar.Block - baseWidget + format Format + baseWidget `yaml:",inline"` } func (w *TemperatureMonitor) Run(ctx context.Context, update statusbar.UpdateFunc) { w.init() - w.status = make([]statusbar.Block, 0, 2) w.emitStatus(update) t := time.NewTicker(1 * time.Second) for { @@ -51,7 +49,6 @@ func (w *TemperatureMonitor) readInput(input string) (int, error) { } func (w *TemperatureMonitor) emitStatus(update statusbar.UpdateFunc) { - w.status = w.status[:0] temp, err := w.readInput(w.TempInput) if err != nil { w.errorf(err.Error()) @@ -62,14 +59,12 @@ func (w *TemperatureMonitor) emitStatus(update statusbar.UpdateFunc) { w.errorf(err.Error()) fan = 0 } - w.status = append(w.status, statusbar.Block{ - FullText: w.format.Format(temp/1000, fan), - Align: "left", - Name: "batmon", - Instance: "0", - Markup: "pango", - }) - update(w.status) + b := w.Block() + b.FullText = w.format.Format(temp/1000, fan) + b.Name = "batmon" + b.Instance = "0" + b.Markup = "pango" + update([]statusbar.Block{b}) } func (w *TemperatureMonitor) init() { diff --git a/widgets/volume.go b/widgets/volume.go index 584f5d7..ec6a37f 100644 --- a/widgets/volume.go +++ b/widgets/volume.go @@ -35,7 +35,7 @@ type Volume struct { rc io.ReadCloser procErr error - baseWidget + baseWidget `yaml:",inline"` } func (w *Volume) Run(ctx context.Context, update statusbar.UpdateFunc) { @@ -119,15 +119,12 @@ func (w *Volume) emitVol(vol int, mute bool, update statusbar.UpdateFunc) { icon = w.MuteIcon } status := w.fmt.Format(icon, vol) - update([]statusbar.Block{ - { - FullText: status, - Align: "left", - Name: "volmon", - Instance: "0", - Markup: "pango", - }, - }) + b := w.Block() + b.FullText = status + b.Name = "volmon" + b.Instance = "0" + b.Markup = "pango" + update([]statusbar.Block{b}) } func (w *Volume) init() { diff --git a/widgets/window.go b/widgets/window.go index 943f06b..67c199d 100644 --- a/widgets/window.go +++ b/widgets/window.go @@ -21,7 +21,8 @@ type Window struct { events chan *swayipc.WindowEvent conn *swayipc.Conn update statusbar.UpdateFunc - baseWidget + + baseWidget `yaml:",inline"` } func (w *Window) Run(ctx context.Context, update statusbar.UpdateFunc) { @@ -188,15 +189,12 @@ func (w *Window) parseNode(c *swayipc.Node) string { } func (w *Window) printTitle(title string) { - w.update([]statusbar.Block{ - { - FullText: title, - Align: "left", - Name: "window", - Instance: "0", - Markup: "pango", - }, - }) + b := w.Block() + b.FullText = title + b.Name = "window" + b.Instance = "0" + b.Markup = "pango" + w.update([]statusbar.Block{b}) } func (w *Window) init() { -- 2.45.2