From 90850ae749379c00d8d1ba78cc9f6c1a4176c358 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 7 Jun 2021 03:38:23 +0200 Subject: [PATCH] Allow for non-string config map values --- config.go | 52 +++++++++++++++++++++++++++++++-- deck.go | 5 +++- decks/main.deck | 13 +++++---- go.sum | 1 - widget.go | 64 +++++++++++------------------------------ widget_button.go | 17 +++++++++++ widget_recent_window.go | 12 ++++++++ widget_time.go | 56 ++++++++++++++++++++++-------------- widget_top.go | 22 ++++++++++++-- 9 files changed, 161 insertions(+), 81 deletions(-) diff --git a/config.go b/config.go index 3ee4eef..87e8a45 100644 --- a/config.go +++ b/config.go @@ -2,7 +2,10 @@ package main import ( "bytes" + "fmt" "io/ioutil" + "reflect" + "strconv" "github.com/BurntSushi/toml" ) @@ -26,9 +29,9 @@ type ActionConfig struct { // WidgetConfig describes configuration data for widgets. type WidgetConfig struct { - ID string `toml:"id,omitempty"` - Interval uint `toml:"interval,omitempty"` - Config map[string]string `toml:"config,omitempty"` + ID string `toml:"id,omitempty"` + Interval uint `toml:"interval,omitempty"` + Config map[string]interface{} `toml:"config,omitempty"` } // KeyConfig holds the entire configuration for a single key. @@ -71,3 +74,46 @@ func (c DeckConfig) Save(filename string) error { return ioutil.WriteFile(filename, b.Bytes(), 0600) } + +func ConfigValue(v interface{}, dst interface{}) error { + switch d := dst.(type) { + case *string: + switch vt := v.(type) { + case string: + *d = vt + default: + return fmt.Errorf("unhandled type %+v for string conversion", reflect.TypeOf(vt)) + } + + case *int64: + switch vt := v.(type) { + case int64: + *d = vt + case float64: + *d = int64(vt) + case string: + x, _ := strconv.ParseInt(vt, 0, 64) + *d = int64(x) + default: + return fmt.Errorf("unhandled type %+v for uint8 conversion", reflect.TypeOf(vt)) + } + + case *float64: + switch vt := v.(type) { + case int64: + *d = float64(vt) + case float64: + *d = vt + case string: + x, _ := strconv.ParseFloat(vt, 64) + *d = float64(x) + default: + return fmt.Errorf("unhandled type %+v for float64 conversion", reflect.TypeOf(vt)) + } + + default: + return fmt.Errorf("unhandled dst type %+v", reflect.TypeOf(dst)) + } + + return nil +} diff --git a/deck.go b/deck.go index 231660d..7f7d986 100644 --- a/deck.go +++ b/deck.go @@ -60,7 +60,10 @@ func LoadDeck(dev *streamdeck.Device, base string, deck string) (*Deck, error) { var w Widget if k, found := keyMap[i]; found { - w = NewWidget(k, bg) + w, err = NewWidget(k, bg) + if err != nil { + return nil, err + } } else { w = NewBaseWidget(i, nil, nil, bg) } diff --git a/decks/main.deck b/decks/main.deck index 50d662e..982d27c 100644 --- a/decks/main.deck +++ b/decks/main.deck @@ -12,7 +12,6 @@ id = "top" [keys.widget.config] mode = "memory" - fillColor = "#a69bd6" [[keys]] index = 3 @@ -37,6 +36,7 @@ [keys.widget.config] icon = "assets/volume-low.png" label = "Lower Vol" + fontsize = 8 [keys.action] keycode = "VolumeDown" @@ -47,6 +47,7 @@ [keys.widget.config] icon = "assets/volume-high.png" label = "Raise Vol" + fontsize = 8 [keys.action] keycode = "VolumeUp" @@ -87,32 +88,32 @@ [keys.widget] id = "recentWindow" [keys.widget.config] - window = "1" + window = 1 [[keys]] index = 11 [keys.widget] id = "recentWindow" [keys.widget.config] - window = "2" + window = 2 [[keys]] index = 12 [keys.widget] id = "recentWindow" [keys.widget.config] - window = "3" + window = 3 [[keys]] index = 13 [keys.widget] id = "recentWindow" [keys.widget.config] - window = "4" + window = 4 [[keys]] index = 14 [keys.widget] id = "recentWindow" [keys.widget.config] - window = "5" + window = 5 diff --git a/go.sum b/go.sum index 4c95e21..3382bdf 100644 --- a/go.sum +++ b/go.sum @@ -125,7 +125,6 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= diff --git a/widget.go b/widget.go index 941846f..6b1dfff 100644 --- a/widget.go +++ b/widget.go @@ -1,12 +1,12 @@ package main import ( + "fmt" "image" "image/color" "image/draw" "log" "os" - "strconv" "time" "github.com/golang/freetype" @@ -82,67 +82,37 @@ func NewBaseWidget(index uint8, action, actionHold *ActionConfig, bg image.Image } // NewWidget initializes a widget. -func NewWidget(kc KeyConfig, bg image.Image) Widget { +func NewWidget(kc KeyConfig, bg image.Image) (Widget, error) { bw := NewBaseWidget(kc.Index, kc.Action, kc.ActionHold, bg) - wc := kc.Widget - switch wc.ID { + switch kc.Widget.ID { case "button": - bw.setInterval(wc.Interval, 0) - return &ButtonWidget{ - BaseWidget: *bw, - icon: wc.Config["icon"], - label: wc.Config["label"], - } + return NewButtonWidget(*bw, kc.Widget) case "clock": - bw.setInterval(wc.Interval, 1000) - return &TimeWidget{ - BaseWidget: *bw, - format: "%H;%i;%s", - font: "bold;regular;thin", - } + kc.Widget.Config = make(map[string]interface{}) + kc.Widget.Config["format"] = "%H;%i;%s" + kc.Widget.Config["font"] = "bold;regular;thin" + return NewTimeWidget(*bw, kc.Widget) case "date": - bw.setInterval(wc.Interval, 1000) - return &TimeWidget{ - BaseWidget: *bw, - format: "%l;%d;%M", - font: "regular;bold;regular", - } + kc.Widget.Config = make(map[string]interface{}) + kc.Widget.Config["format"] = "%l;%d;%M" + kc.Widget.Config["font"] = "regular;bold;regular" + return NewTimeWidget(*bw, kc.Widget) case "time": - bw.setInterval(wc.Interval, 1000) - return &TimeWidget{ - BaseWidget: *bw, - format: wc.Config["format"], - font: wc.Config["font"], - } + return NewTimeWidget(*bw, kc.Widget) case "recentWindow": - i, err := strconv.ParseUint(wc.Config["window"], 10, 64) - if err != nil { - log.Fatal(err) - } - return &RecentWindowWidget{ - BaseWidget: *bw, - window: uint8(i), - } + return NewRecentWindowWidget(*bw, kc.Widget) case "top": - bw.setInterval(wc.Interval, 500) - return &TopWidget{ - BaseWidget: *bw, - mode: wc.Config["mode"], - fillColor: wc.Config["fillColor"], - } - - default: - // unknown widget ID - log.Println("Unknown widget with ID:", wc.ID) + return NewTopWidget(*bw, kc.Widget) } - return nil + // unknown widget ID + return nil, fmt.Errorf("Unknown widget with ID %s", kc.Widget.ID) } // renders the widget including its background image. diff --git a/widget_button.go b/widget_button.go index 83c6440..4186d9f 100644 --- a/widget_button.go +++ b/widget_button.go @@ -15,6 +15,23 @@ type ButtonWidget struct { fontsize float64 } +func NewButtonWidget(bw BaseWidget, opts WidgetConfig) (*ButtonWidget, error) { + bw.setInterval(opts.Interval, 0) + + var icon, label string + ConfigValue(opts.Config["icon"], &icon) + ConfigValue(opts.Config["label"], &label) + var fontsize float64 + ConfigValue(opts.Config["fontsize"], &fontsize) + + return &ButtonWidget{ + BaseWidget: bw, + icon: icon, + label: label, + fontsize: fontsize, + }, nil +} + // Update renders the widget. func (w *ButtonWidget) Update(dev *streamdeck.Device) error { size := int(dev.Pixels) diff --git a/widget_recent_window.go b/widget_recent_window.go index 299aef9..2226b14 100644 --- a/widget_recent_window.go +++ b/widget_recent_window.go @@ -17,6 +17,18 @@ type RecentWindowWidget struct { lastClass string } +func NewRecentWindowWidget(bw BaseWidget, opts WidgetConfig) (*RecentWindowWidget, error) { + var window int64 + if err := ConfigValue(opts.Config["window"], &window); err != nil { + return nil, err + } + + return &RecentWindowWidget{ + BaseWidget: bw, + window: uint8(window), + }, nil +} + // RequiresUpdate returns true when the widget wants to be repainted. func (w *RecentWindowWidget) RequiresUpdate() bool { if int(w.window) < len(recentWindows) { diff --git a/widget_time.go b/widget_time.go index edce63b..9eeac15 100644 --- a/widget_time.go +++ b/widget_time.go @@ -17,29 +17,18 @@ type TimeWidget struct { font string } -func formatTime(t time.Time, format string) string { - tm := map[string]string{ - "%Y": "2006", - "%y": "06", - "%F": "January", - "%M": "Jan", - "%m": "01", - "%l": "Monday", - "%D": "Mon", - "%d": "02", - "%h": "03", - "%H": "15", - "%i": "04", - "%s": "05", - "%a": "PM", - "%t": "MST", - } +func NewTimeWidget(bw BaseWidget, opts WidgetConfig) (*TimeWidget, error) { + bw.setInterval(opts.Interval, 500) - for k, v := range tm { - format = strings.ReplaceAll(format, k, v) - } + var format, font string + ConfigValue(opts.Config["format"], &format) + ConfigValue(opts.Config["font"], &font) - return t.Format(format) + return &TimeWidget{ + BaseWidget: bw, + format: format, + font: font, + }, nil } // Update renders the widget. @@ -75,3 +64,28 @@ func (w *TimeWidget) Update(dev *streamdeck.Device) error { return w.render(dev, img) } + +func formatTime(t time.Time, format string) string { + tm := map[string]string{ + "%Y": "2006", + "%y": "06", + "%F": "January", + "%M": "Jan", + "%m": "01", + "%l": "Monday", + "%D": "Mon", + "%d": "02", + "%h": "03", + "%H": "15", + "%i": "04", + "%s": "05", + "%a": "PM", + "%t": "MST", + } + + for k, v := range tm { + format = strings.ReplaceAll(format, k, v) + } + + return t.Format(format) +} diff --git a/widget_top.go b/widget_top.go index 928887f..d95e4e3 100644 --- a/widget_top.go +++ b/widget_top.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "image" "image/color" "image/draw" @@ -22,6 +23,20 @@ type TopWidget struct { lastValue float64 } +func NewTopWidget(bw BaseWidget, opts WidgetConfig) (*TopWidget, error) { + bw.setInterval(opts.Interval, 500) + + var mode, fillColor string + ConfigValue(opts.Config["mode"], &mode) + ConfigValue(opts.Config["fillColor"], &fillColor) + + return &TopWidget{ + BaseWidget: bw, + mode: mode, + fillColor: fillColor, + }, nil +} + // Update renders the widget. func (w *TopWidget) Update(dev *streamdeck.Device) error { var value float64 @@ -46,7 +61,7 @@ func (w *TopWidget) Update(dev *streamdeck.Device) error { label = "MEM" default: - panic("Unknown widget mode: " + w.mode) + return fmt.Errorf("unknown widget mode: %s", w.mode) } if w.lastValue == value { @@ -54,9 +69,12 @@ func (w *TopWidget) Update(dev *streamdeck.Device) error { } w.lastValue = value + if w.fillColor == "" { + w.fillColor = "#a69bd6" + } fill, err := colorful.Hex(w.fillColor) if err != nil { - panic("Invalid color: " + w.fillColor) + return fmt.Errorf("invalid color: %s", w.fillColor) } size := int(dev.Pixels)