From d76d56b393e1edcf1df5b2dbd8bcbf42cefcc2e5 Mon Sep 17 00:00:00 2001 From: Jim Ramsay Date: Fri, 29 Jul 2022 23:41:51 -0400 Subject: [PATCH] Initial waybar plugin framework for go Signed-off-by: Jim Ramsay --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++++- go.mod | 12 ++++++++++++ go.sum | 17 +++++++++++++++++ status.go | 31 +++++++++++++++++++++++++++++++ status_test.go | 30 ++++++++++++++++++++++++++++++ updater.go | 41 +++++++++++++++++++++++++++++++++++++++++ updater_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 status.go create mode 100644 status_test.go create mode 100644 updater.go create mode 100644 updater_test.go diff --git a/README.md b/README.md index 75423e5..ddf8b1a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,44 @@ # gowaybarplug -Go framework for + +Go framework for custom [Waybar](https://github.com/Alexays/Waybar) plugins + +# Usage + +First build a plugin that reports some interesting status: + +```go +package main + +import ( + "time" + + waybar "github.com/lack/gowaybarplug" +) + +main() { + updater := waybar.NewUpdater() + for { + status := waybar.Status{ + Text: "Some text", + Toolbar: "Other text", + } + // Obviously do something more interesting than just static text in the status... + updater.Status <- &status + time.Sleep(15 * time.Second) + } +} +``` + +Then add it to your ~/.config/waybar/config: + +```json +{ + // ... Other waybar config + "custom/mything": { + "format": "{} {icon}", + "return-type": "json", + "exec": "/path/to/my/new/plugin" + // etc + } +} +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e552e6b --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/lack/gowaybarplug + +go 1.18 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect + github.com/stretchr/testify v1.8.0 // indirect + github.com/xorcare/pointer v1.1.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..748d110 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= +github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/status.go b/status.go new file mode 100644 index 0000000..9248bf0 --- /dev/null +++ b/status.go @@ -0,0 +1,31 @@ +package gowaybarplug + +import ( + "encoding/json" + "fmt" +) + +// Status represents the json structure expected from waybar custom plugins. +// See waybar-custom(5) for more details. +type Status struct { + // Text is usually the label, represented by {} in a waybar format + Text string `json:"text"` + // Tooltip appears when you mouse-hover over the custom entry + Tooltip string `json:"tooltip,omitempty"` + // Class is a list of css classes that will be added to the waybat entry + Class []string `json:"class,omitempty"` + // Percentage can be added to format strings via the {percent} format string, but can also affect which icon is set in {icon} if the config specifies format-icons as an array. + Percentage *int `json:"percentage,omitempty"` + // Alt os the key used to look up the {icon} of format-icons is specified as a map. + Alt string `json:"alt,omitempty"` +} + +// String renders the Status as a json string +func (s *Status) String() string { + b, err := json.Marshal(s) + if err != nil { + // Fake json error reporting + return fmt.Sprintf(`"text": "Marshal error", "tooltip": "Marshal error: %s"}`, err.Error()) + } + return string(b) +} diff --git a/status_test.go b/status_test.go new file mode 100644 index 0000000..118dbf7 --- /dev/null +++ b/status_test.go @@ -0,0 +1,30 @@ +package gowaybarplug + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/xorcare/pointer" +) + +func TestString(t *testing.T) { + tests := []struct { + input Status + output string + }{{ + input: Status{}, + output: `{"text":""}`, + }, { + input: Status{ + Text: "a", + Tooltip: "b", + Class: []string{"c1", "c2"}, + Percentage: pointer.Int(42), + Alt: "d", + }, + output: `{"text":"a","tooltip":"b","class":["c1","c2"],"percentage":42,"alt":"d"}`, + }} + for _, test := range tests { + assert.Equal(t, test.input.String(), test.output) + } +} diff --git a/updater.go b/updater.go new file mode 100644 index 0000000..07f0908 --- /dev/null +++ b/updater.go @@ -0,0 +1,41 @@ +package gowaybarplug + +import ( + "fmt" + "io" + "os" +) + +// Updater runs a goroutine that accepts Status updates in a channel and ptints them to stdout. +type Updater struct { + // Status is the main Status reporting channel. Every Status submitted here will be sent to stdout. + Status chan *Status + + last string + writer io.Writer +} + +// Create a new Updater and start the receiver thread +func NewUpdater() *Updater { + u := Updater{ + Status: make(chan *Status, 10), + writer: os.Stdout, + } + go u.run() + return &u +} + +func (u *Updater) run() { + for s := range u.Status { + next := s.String() + if next != u.last { + u.last = next + fmt.Fprintln(u.writer, next) + } + } +} + +func (u *Updater) OutputTo(writer io.Writer) *Updater { + u.writer = writer + return u +} diff --git a/updater_test.go b/updater_test.go new file mode 100644 index 0000000..4c8ca32 --- /dev/null +++ b/updater_test.go @@ -0,0 +1,43 @@ +package gowaybarplug + +import ( + "bytes" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type ChBuf struct { + Ch chan bool + Buff bytes.Buffer +} + +func NewChBuf() *ChBuf { + return &ChBuf{ + Ch: make(chan bool, 100), + } +} + +func (c *ChBuf) Write(data []byte) (int, error) { + n, err := c.Buff.Write(data) + c.Ch <- err == nil + return n, err +} + +func (c *ChBuf) WaitForBuffer(deadline time.Duration) { + select { + case <-c.Ch: + case <-time.After(deadline): + } +} + +func TestNewUpdater(t *testing.T) { + buff := NewChBuf() + u := NewUpdater().OutputTo(buff) + u.Ch <- &Status{Text: "test"} + buff.WaitForBuffer(2 * time.Second) + result, err := buff.Buff.ReadString('\n') + assert.NoError(t, err) + assert.Equal(t, result, "{\"text\":\"test\"}\n") +}