M README.md => README.md +44 -3
@@ 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
M configuration.go => configuration.go +6 -1
@@ 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)
M configuration_test.go => configuration_test.go +12 -7
@@ 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])
}
}
M swaybarproto.go => swaybarproto.go +90 -16
@@ 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
+ }
+}
A swaybarproto_test.go => swaybarproto_test.go +39 -0
@@ 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])
+ }
+ }
+}
M widget.go => widget.go +18 -9
@@ 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
}
M widgets/batmon.go => widgets/batmon.go +9 -11
@@ 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
M widgets/date.go => widgets/date.go +9 -34
@@ 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() {
M widgets/sysmon.go => widgets/sysmon.go +0 -1
@@ 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"
M widgets/temp.go => widgets/temp.go +8 -13
@@ 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() {
M widgets/volume.go => widgets/volume.go +7 -10
@@ 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() {
M widgets/window.go => widgets/window.go +8 -10
@@ 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() {