1
0
Fork 0
mirror of https://github.com/Luzifer/streamdeck.git synced 2024-10-18 05:04:18 +00:00

Move configuration to more stable format (#8)

This commit is contained in:
Knut Ahlers 2021-02-26 23:08:55 +01:00 committed by GitHub
parent 393ba030a2
commit ecdf4967ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 207 additions and 295 deletions

View file

@ -14,46 +14,25 @@ func init() {
type actionExec struct{}
func (actionExec) Execute(attributes map[string]interface{}) error {
cmd, ok := attributes["command"].([]interface{})
if !ok {
func (actionExec) Execute(attributes attributeCollection) error {
if attributes.Command == nil {
return errors.New("No command supplied")
}
var args []string
for _, c := range cmd {
if v, ok := c.(string); ok {
args = append(args, v)
continue
}
return errors.New("Command conatins non-string argument")
}
processEnv := env.ListToMap(os.Environ())
if e, ok := attributes["env"].(map[interface{}]interface{}); ok {
for k, v := range e {
key, ok := k.(string)
if !ok {
continue
}
value, ok := v.(string)
if !ok {
continue
}
processEnv[key] = value
}
for k, v := range attributes.Env {
processEnv[k] = v
}
command := exec.Command(args[0], args[1:]...)
command := exec.Command(attributes.Command[0], attributes.Command[1:]...)
command.Env = env.MapToList(processEnv)
if v, ok := attributes["attach_stdout"].(bool); ok && v {
if attributes.AttachStdout {
command.Stdout = os.Stdout
}
if v, ok := attributes["attach_stderr"].(bool); ok && v {
if attributes.AttachStderr {
command.Stdout = os.Stderr
}
@ -61,8 +40,8 @@ func (actionExec) Execute(attributes map[string]interface{}) error {
return errors.Wrap(err, "Unable to start command")
}
// If "wait" is set and set to true start command and wait for execution
if v, ok := attributes["wait"].(bool); ok && v {
// If "wait" is set to true start command and wait for execution
if attributes.Wait {
return errors.Wrap(command.Wait(), "Command was not successful")
}

View file

@ -1,7 +1,6 @@
package main
import (
"strconv"
"time"
"github.com/pkg/errors"
@ -14,71 +13,53 @@ func init() {
type actionKeyPress struct{}
func (actionKeyPress) Execute(attributes map[string]interface{}) error {
func (actionKeyPress) Execute(attributes attributeCollection) error {
var (
delay time.Duration
execCodes []uint16
ok bool
keyNames []interface{}
keyCodes []interface{}
keyNames []string
keyCodes []int
useKeyNames bool
)
keyCodes, ok = attributes["key_codes"].([]interface{})
if !ok {
keyNames, ok = attributes["keys"].([]interface{})
if !ok {
keyCodes = attributes.KeyCodes
if keyCodes == nil {
keyNames = attributes.Keys
if keyNames == nil {
return errors.New("No key_codes or keys array present")
}
useKeyNames = true
}
if v, ok := attributes["delay"].(string); ok {
if d, err := time.ParseDuration(v); err == nil {
delay = d
}
}
if useKeyNames {
for _, k := range keyNames {
// Convert misdetections into strings
switch k.(type) {
case int:
k = strconv.Itoa(k.(int))
}
if kv, ok := k.(string); ok {
if kc, ok := uinputKeyMapping[kv]; ok {
execCodes = append(execCodes, kc)
} else {
return errors.Errorf("Unknown key %q", kv)
}
if kc, ok := uinputKeyMapping[k]; ok {
execCodes = append(execCodes, kc)
} else {
return errors.New("Unknown key type detected")
return errors.Errorf("Unknown key %q", k)
}
}
} else {
for _, k := range keyCodes {
execCodes = append(execCodes, uint16(k.(int)))
execCodes = append(execCodes, uint16(k))
}
}
if v, ok := attributes["mod_shift"].(bool); ok && v {
if attributes.ModShift {
if err := kbd.KeyDown(uinput.KeyLeftShift); err != nil {
return errors.Wrap(err, "Unable to set shift")
}
defer kbd.KeyUp(uinput.KeyLeftShift)
}
if v, ok := attributes["mod_alt"].(bool); ok && v {
if attributes.ModAlt {
if err := kbd.KeyDown(uinput.KeyLeftAlt); err != nil {
return errors.Wrap(err, "Unable to set shift")
}
defer kbd.KeyUp(uinput.KeyLeftAlt)
}
if v, ok := attributes["mod_ctrl"].(bool); ok && v {
if attributes.ModCtrl {
if err := kbd.KeyDown(uinput.KeyLeftCtrl); err != nil {
return errors.Wrap(err, "Unable to set shift")
}
@ -89,7 +70,7 @@ func (actionKeyPress) Execute(attributes map[string]interface{}) error {
if err := kbd.KeyPress(kc); err != nil {
return errors.Wrap(err, "Unable to press key")
}
time.Sleep(delay)
time.Sleep(attributes.Delay)
}
return nil

View file

@ -12,15 +12,12 @@ func init() {
type actionPage struct{}
func (actionPage) Execute(attributes map[string]interface{}) error {
name, nameOk := attributes["name"].(string)
relative, relativeOk := attributes["relative"].(int)
if nameOk && name != "" {
return errors.Wrap(togglePage(name), "switch page")
func (actionPage) Execute(attributes attributeCollection) error {
if attributes.Name != "" {
return errors.Wrap(togglePage(attributes.Name), "switch page")
}
if absRel := int(math.Abs(float64(relative))); relativeOk && absRel != 0 && absRel < len(pageStack) {
if absRel := int(math.Abs(float64(attributes.Relative))); absRel != 0 && absRel < len(pageStack) {
nextPage := pageStack[absRel]
pageStack = pageStack[absRel+1:]
return errors.Wrap(togglePage(nextPage), "switch relative page")

View file

@ -3,8 +3,6 @@
package main
import (
"strconv"
"github.com/pkg/errors"
)
@ -14,86 +12,56 @@ func init() {
type actionPulseVolume struct{}
func (actionPulseVolume) Execute(attributes map[string]interface{}) error {
func (actionPulseVolume) Execute(attributes attributeCollection) error {
if pulseClient == nil {
return errors.New("PulseAudio client not initialized")
}
devType, ok := attributes["device"].(string)
if !ok {
if attributes.Device == "" {
return errors.New("Missing 'device' attribute")
}
match, ok := attributes["match"].(string)
if !ok {
if attributes.Match == "" {
return errors.New("Missing 'match' attribute")
}
// Read mute value
var (
mute string
mutev = attributes["mute"]
)
switch mutev.(type) {
case string:
mute = mutev.(string)
case bool:
mute = strconv.FormatBool(mutev.(bool))
}
// Read volume
var (
volAbs bool
volVal float64
)
for attr, abs := range map[string]bool{
"set_volume": true,
"change_volume": false,
} {
val, ok := attributes[attr]
if !ok {
continue
}
switch val.(type) {
case float64:
volVal = val.(float64) / 100
case int:
volVal = float64(val.(int)) / 100
case int64:
volVal = float64(val.(int64)) / 100
}
volAbs = abs
break
if attributes.SetVolume != nil {
volVal = *attributes.SetVolume / 100
volAbs = true
} else if attributes.ChangeVolume != nil {
volVal = *attributes.ChangeVolume / 100
volAbs = false
}
// Execute change
switch devType {
switch attributes.Device {
case "input":
return errors.Wrap(
pulseClient.SetSinkInputVolume(match, mute, volVal, volAbs),
pulseClient.SetSinkInputVolume(attributes.Match, attributes.Mute, volVal, volAbs),
"Unable to set sink input volume",
)
case "sink":
return errors.Wrap(
pulseClient.SetSinkVolume(match, mute, volVal, volAbs),
pulseClient.SetSinkVolume(attributes.Match, attributes.Mute, volVal, volAbs),
"Unable to set sink volume",
)
case "source":
return errors.Wrap(
pulseClient.SetSourceVolume(match, mute, volVal, volAbs),
pulseClient.SetSourceVolume(attributes.Match, attributes.Mute, volVal, volAbs),
"Unable to set source volume",
)
default:
return errors.Errorf("Unsupported device type: %q", devType)
return errors.Errorf("Unsupported device type: %q", attributes.Device)
}
}

View file

@ -8,7 +8,7 @@ func init() {
type actionReloadConfig struct{}
func (actionReloadConfig) Execute(attributes map[string]interface{}) error {
func (actionReloadConfig) Execute(attributes attributeCollection) error {
if err := loadConfig(); err != nil {
return errors.Wrap(err, "Unable to reload config")
}

View file

@ -10,7 +10,7 @@ var actionToggleDisplayPreviousBrightness int
type actionToggleDisplay struct{}
func (actionToggleDisplay) Execute(attributes map[string]interface{}) error {
func (actionToggleDisplay) Execute(attributes attributeCollection) error {
var newB int
if currentBrightness > 0 {
actionToggleDisplayPreviousBrightness = currentBrightness

View file

@ -1,6 +1,9 @@
package main
import (
"bytes"
"encoding/gob"
"image/color"
"os"
"time"
@ -10,36 +13,90 @@ import (
const defaultLongPressDuration = 500 * time.Millisecond
func init() {
gob.Register(attributeCollection{})
}
type attributeCollection struct {
AttachStderr bool `json:"attach_stderr,omitempty" yaml:"attach_stderr,omitempty"`
AttachStdout bool `json:"attach_stdout,omitempty" yaml:"attach_stdout,omitempty"`
Border *int `json:"border,omitempty" yaml:"border,omitempty"`
Caption string `json:"caption,omitempty" yaml:"caption,omitempty"`
ChangeVolume *float64 `json:"change_volume,omitempty" yaml:"change_volume,omitempty"`
Color string `json:"color,omitempty" yaml:"color,omitempty"`
Command []string `json:"command,omitempty" yaml:"command,omitempty"`
Delay time.Duration `json:"delay,omitempty" yaml:"delay,omitempty"`
Device string `json:"device,omitempty" yaml:"device,omitempty"`
Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"`
FontSize *float64 `json:"font_size,omitempty" yaml:"font_size,omitempty"`
Image string `json:"image,omitempty" yaml:"image,omitempty"`
Interval time.Duration `json:"interval,omitempty" yaml:"interval,omitempty"`
KeyCodes []int `json:"key_codes,omitempty" yaml:"key_codes,omitempty"`
Keys []string `json:"keys,omitempty" yaml:"keys,omitempty"`
Match string `json:"match,omitempty" yaml:"match,omitempty"`
ModAlt bool `json:"mod_alt,omitempty" yaml:"mod_alt,omitempty"`
ModCtrl bool `json:"mod_ctrl,omitempty" yaml:"mod_ctrl,omitempty"`
ModShift bool `json:"mod_shift,omitempty" yaml:"mod_shift,omitempty"`
Mute string `json:"mute,omitempty" yaml:"mute,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Relative int `json:"relative,omitempty" yaml:"relative,omitempty"`
RGBA []int `json:"rgba,omitempty" yaml:"rgba,omitempty"`
SetVolume *float64 `json:"set_volume,omitempty" yaml:"set_volume,omitempty"`
Text string `json:"text,omitempty" yaml:"text,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Wait bool `json:"wait,omitempty" yaml:"wait,omitempty"`
}
func (a attributeCollection) Clone() attributeCollection {
var (
buf = new(bytes.Buffer)
out attributeCollection
)
gob.NewEncoder(buf).Encode(a)
gob.NewDecoder(buf).Decode(&out)
return out
}
func (a attributeCollection) RGBAToColor() color.RGBA {
if len(a.RGBA) != 4 {
return color.RGBA{}
}
return color.RGBA{uint8(a.RGBA[0]), uint8(a.RGBA[1]), uint8(a.RGBA[2]), uint8(a.RGBA[3])}
}
type config struct {
AutoReload bool `yaml:"auto_reload"`
CaptionBorder int `yaml:"caption_border"`
CaptionColor [4]int `yaml:"caption_color"`
CaptionFont string `yaml:"caption_font"`
CaptionFontSize float64 `yaml:"caption_font_size"`
CaptionPosition captionPosition `yaml:"caption_position"`
DefaultBrightness int `yaml:"default_brightness"`
DefaultPage string `yaml:"default_page"`
DisplayOffTime time.Duration `yaml:"display_off_time"`
LongPressDuration time.Duration `yaml:"long_press_duration"`
Pages map[string]page `yaml:"pages"`
RenderFont string `yaml:"render_font"`
AutoReload bool `json:"auto_reload" yaml:"auto_reload"`
CaptionBorder int `json:"caption_border" yaml:"caption_border"`
CaptionColor [4]int `json:"caption_color" yaml:"caption_color"`
CaptionFont string `json:"caption_font" yaml:"caption_font"`
CaptionFontSize float64 `json:"caption_font_size" yaml:"caption_font_size"`
CaptionPosition captionPosition `json:"caption_position" yaml:"caption_position"`
DefaultBrightness int `json:"default_brightness" yaml:"default_brightness"`
DefaultPage string `json:"default_page" yaml:"default_page"`
DisplayOffTime time.Duration `json:"display_off_time" yaml:"display_off_time"`
LongPressDuration time.Duration `json:"long_press_duration" yaml:"long_press_duration"`
Pages map[string]page `json:"pages" yaml:"pages"`
RenderFont string `json:"render_font" yaml:"render_font"`
}
type page struct {
Keys map[int]keyDefinition `yaml:"keys"`
Overlay string `yaml:"overlay"`
Underlay string `yaml:"underlay"`
Keys map[int]keyDefinition `json:"keys" yaml:"keys"`
Overlay string `json:"overlay" yaml:"overlay"`
Underlay string `json:"underlay" yaml:"underlay"`
}
type keyDefinition struct {
Display dynamicElement `yaml:"display"`
Actions []dynamicElement `yaml:"actions"`
Display dynamicElement `json:"display" yaml:"display"`
Actions []dynamicElement `json:"actions" yaml:"actions"`
}
type dynamicElement struct {
Type string `yaml:"type"`
LongPress bool `yaml:"long_press"`
Attributes map[string]interface{} `yaml:"attributes"`
Type string `json:"type" yaml:"type"`
LongPress bool `json:"long_press" yaml:"long_press"`
Attributes attributeCollection `json:"attributes" yaml:"attributes"`
}
func newConfig() config {

View file

@ -13,13 +13,13 @@ func init() {
type displayElementColor struct{}
func (d displayElementColor) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
if name, ok := attributes["color"].(string); ok {
return d.displayPredefinedColor(idx, name)
func (d displayElementColor) Display(ctx context.Context, idx int, attributes attributeCollection) error {
if attributes.Color != "" {
return d.displayPredefinedColor(idx, attributes.Color)
}
if rgba, ok := attributes["rgba"].([]interface{}); ok {
if len(rgba) != 4 {
if attributes.RGBA != nil {
if len(attributes.RGBA) != 4 {
return errors.New("RGBA color definition needs 4 hex values")
}
@ -28,7 +28,7 @@ func (d displayElementColor) Display(ctx context.Context, idx int, attributes ma
return err
}
return sd.FillColor(idx, color.RGBA{uint8(rgba[0].(int)), uint8(rgba[1].(int)), uint8(rgba[2].(int)), uint8(rgba[3].(int))})
return sd.FillColor(idx, attributes.RGBAToColor())
}
return errors.New("No valid attributes specified for type color")

View file

@ -25,48 +25,27 @@ type displayElementExec struct {
running bool
}
func (d displayElementExec) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
func (d displayElementExec) Display(ctx context.Context, idx int, attributes attributeCollection) error {
var (
err error
imgRenderer = newTextOnImageRenderer()
)
// Initialize command
cmd, ok := attributes["command"].([]interface{})
if !ok {
if attributes.Command == nil {
return errors.New("No command supplied")
}
var args []string
for _, c := range cmd {
if v, ok := c.(string); ok {
args = append(args, v)
continue
}
return errors.New("Command conatins non-string argument")
}
// Execute command and parse it
buf := new(bytes.Buffer)
processEnv := env.ListToMap(os.Environ())
if e, ok := attributes["env"].(map[interface{}]interface{}); ok {
for k, v := range e {
key, ok := k.(string)
if !ok {
continue
}
value, ok := v.(string)
if !ok {
continue
}
processEnv[key] = value
}
for k, v := range attributes.Env {
processEnv[k] = v
}
command := exec.Command(args[0], args[1:]...)
command := exec.Command(attributes.Command[0], attributes.Command[1:]...)
command.Env = env.MapToList(processEnv)
command.Stdout = buf
@ -74,65 +53,50 @@ func (d displayElementExec) Display(ctx context.Context, idx int, attributes map
return errors.Wrap(err, "Command has exit != 0")
}
attributes["text"] = strings.TrimSpace(buf.String())
attributes.Text = strings.TrimSpace(buf.String())
tmpAttrs := map[string]interface{}{}
tmpAttrs := attributes.Clone()
if err = json.Unmarshal(buf.Bytes(), &tmpAttrs); err == nil {
// Reset text to empty as it was parsable json
attributes["text"] = ""
for k, v := range tmpAttrs {
attributes[k] = v
}
attributes = tmpAttrs
}
// Initialize background
if filename, ok := attributes["image"].(string); ok {
if err = imgRenderer.DrawBackgroundFromFile(filename); err != nil {
if attributes.Image != "" {
if err = imgRenderer.DrawBackgroundFromFile(attributes.Image); err != nil {
return errors.Wrap(err, "Unable to draw background from disk")
}
}
// Initialize color
var textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff}
if rgba, ok := attributes["color"].([]interface{}); ok {
if len(rgba) != 4 {
if attributes.RGBA != nil {
if len(attributes.RGBA) != 4 {
return errors.New("RGBA color definition needs 4 hex values")
}
tmpCol := color.RGBA{}
for cidx, vp := range []*uint8{&tmpCol.R, &tmpCol.G, &tmpCol.B, &tmpCol.A} {
switch rgba[cidx].(type) {
case int:
*vp = uint8(rgba[cidx].(int))
case float64:
*vp = uint8(rgba[cidx].(float64))
}
}
textColor = tmpCol
textColor = attributes.RGBAToColor()
}
// Initialize fontsize
var fontsize float64 = 120
if v, ok := attributes["font_size"].(float64); ok {
fontsize = v
if attributes.FontSize != nil {
fontsize = *attributes.FontSize
}
border := 10
if v, ok := attributes["border"].(int); ok {
border = v
if attributes.Border != nil {
border = *attributes.Border
}
if strings.TrimSpace(attributes["text"].(string)) != "" {
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes["text"].(string)), fontsize, border, textColor); err != nil {
if strings.TrimSpace(attributes.Text) != "" {
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes.Text), fontsize, border, textColor); err != nil {
return errors.Wrap(err, "Unable to render text")
}
}
if caption, ok := attributes["caption"].(string); ok && strings.TrimSpace(caption) != "" {
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(caption)); err != nil {
if strings.TrimSpace(attributes.Caption) != "" {
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(attributes.Caption)); err != nil {
return errors.Wrap(err, "Unable to render caption")
}
}
@ -149,24 +113,15 @@ func (d displayElementExec) Display(ctx context.Context, idx int, attributes map
return errors.Wrap(sd.FillImage(idx, imgRenderer.GetImage()), "Unable to set image")
}
func (d displayElementExec) NeedsLoop(attributes map[string]interface{}) bool {
if v, ok := attributes["interval"].(int); ok {
return v > 0
}
return false
func (d displayElementExec) NeedsLoop(attributes attributeCollection) bool {
return attributes.Interval > 0
}
func (d *displayElementExec) StartLoopDisplay(ctx context.Context, idx int, attributes map[string]interface{}) error {
func (d *displayElementExec) StartLoopDisplay(ctx context.Context, idx int, attributes attributeCollection) error {
d.running = true
interval := 5 * time.Second
if v, ok := attributes["interval"].(int); ok {
interval = time.Duration(v) * time.Second
}
go func() {
for tick := time.NewTicker(interval); ; <-tick.C {
for tick := time.NewTicker(attributes.Interval); ; <-tick.C {
if !d.running {
return
}

View file

@ -21,22 +21,21 @@ func init() {
type displayElementImage struct{}
func (d displayElementImage) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
filename, ok := attributes["path"].(string)
if !ok {
url, ok := attributes["url"].(string)
if !ok {
func (d displayElementImage) Display(ctx context.Context, idx int, attributes attributeCollection) error {
filename := attributes.Path
if filename == "" {
if attributes.URL == "" {
return errors.New("No path or url attribute specified")
}
var err error
filename, err = d.getCacheFileName(url)
filename, err = d.getCacheFileName(attributes.URL)
if err != nil {
return errors.Wrap(err, "Unable to get cache filename for image url")
}
if _, err := os.Stat(filename); os.IsNotExist(err) {
resp, err := http.Get(url)
resp, err := http.Get(attributes.URL)
if err != nil {
return errors.Wrap(err, "Unable to request image url")
}
@ -84,7 +83,7 @@ func (d displayElementImage) getCacheFileName(url string) (string, error) {
}
cacheDir := path.Join(ucd, "io.luzifer.streamdeck")
if err = os.MkdirAll(cacheDir, 0750); err != nil {
if err = os.MkdirAll(cacheDir, 0o750); err != nil {
return "", errors.Wrap(err, "Unable to create cache dir")
}

View file

@ -19,18 +19,16 @@ func init() {
type displayElementPulseVolume struct{}
func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attributes attributeCollection) error {
if pulseClient == nil {
return errors.New("PulseAudio client not initialized")
}
devType, ok := attributes["device"].(string)
if !ok {
if attributes.Device == "" {
return errors.New("Missing 'device' attribute")
}
match, ok := attributes["match"].(string)
if !ok {
if attributes.Match == "" {
return errors.New("Missing 'match' attribute")
}
@ -41,19 +39,19 @@ func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attribu
volume float64
)
switch devType {
switch attributes.Device {
case "input":
volume, mute, _, _, err = pulseClient.GetSinkInputVolume(match)
volume, mute, _, _, err = pulseClient.GetSinkInputVolume(attributes.Match)
case "sink":
volume, mute, _, _, err = pulseClient.GetSinkVolume(match)
volume, mute, _, _, err = pulseClient.GetSinkVolume(attributes.Match)
case "source":
volume, mute, _, _, err = pulseClient.GetSourceVolume(match)
volume, mute, _, _, err = pulseClient.GetSourceVolume(attributes.Match)
default:
return errors.Errorf("Unsupported device type: %q", devType)
return errors.Errorf("Unsupported device type: %q", attributes.Device)
}
@ -79,42 +77,31 @@ func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attribu
}
// Initialize color
if rgba, ok := attributes["color"].([]interface{}); ok {
if len(rgba) != 4 {
if attributes.RGBA != nil {
if len(attributes.RGBA) != 4 {
return errors.New("RGBA color definition needs 4 hex values")
}
tmpCol := color.RGBA{}
for cidx, vp := range []*uint8{&tmpCol.R, &tmpCol.G, &tmpCol.B, &tmpCol.A} {
switch rgba[cidx].(type) {
case int:
*vp = uint8(rgba[cidx].(int))
case float64:
*vp = uint8(rgba[cidx].(float64))
}
}
textColor = tmpCol
textColor = attributes.RGBAToColor()
}
// Initialize fontsize
var fontsize float64 = 120
if v, ok := attributes["font_size"].(float64); ok {
fontsize = v
if attributes.FontSize != nil {
fontsize = *attributes.FontSize
}
var border = 10
if v, ok := attributes["border"].(int); ok {
border = v
border := 10
if attributes.Border != nil {
border = *attributes.Border
}
if err = img.DrawBigText(text, fontsize, border, textColor); err != nil {
return errors.Wrap(err, "Unable to draw text")
}
if caption, ok := attributes["caption"].(string); ok && strings.TrimSpace(caption) != "" {
if err = img.DrawCaptionText(strings.TrimSpace(caption)); err != nil {
if strings.TrimSpace(attributes.Caption) != "" {
if err = img.DrawCaptionText(strings.TrimSpace(attributes.Caption)); err != nil {
return errors.Wrap(err, "Unable to render caption")
}
}
@ -127,12 +114,12 @@ func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attribu
return errors.Wrap(sd.FillImage(idx, img.GetImage()), "Unable to set image")
}
func (d displayElementPulseVolume) NeedsLoop(attributes map[string]interface{}) bool { return true }
func (d displayElementPulseVolume) NeedsLoop(attributes attributeCollection) bool { return true }
func (d *displayElementPulseVolume) StartLoopDisplay(ctx context.Context, idx int, attributes map[string]interface{}) error {
var interval = time.Second
if v, ok := attributes["interval"].(int); ok {
interval = time.Duration(v) * time.Second
func (d *displayElementPulseVolume) StartLoopDisplay(ctx context.Context, idx int, attributes attributeCollection) error {
interval := time.Second
if attributes.Interval > 0 {
interval = attributes.Interval
}
go func() {

View file

@ -17,59 +17,48 @@ type displayElementText struct {
running bool
}
func (d displayElementText) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
func (d displayElementText) Display(ctx context.Context, idx int, attributes attributeCollection) error {
var (
err error
imgRenderer = newTextOnImageRenderer()
)
// Initialize background
if filename, ok := attributes["image"].(string); ok {
if err = imgRenderer.DrawBackgroundFromFile(filename); err != nil {
if attributes.Image != "" {
if err = imgRenderer.DrawBackgroundFromFile(attributes.Image); err != nil {
return errors.Wrap(err, "Unable to draw background from disk")
}
}
// Initialize color
var textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff}
if rgba, ok := attributes["color"].([]interface{}); ok {
if len(rgba) != 4 {
if attributes.RGBA != nil {
if len(attributes.RGBA) != 4 {
return errors.New("RGBA color definition needs 4 hex values")
}
tmpCol := color.RGBA{}
for cidx, vp := range []*uint8{&tmpCol.R, &tmpCol.G, &tmpCol.B, &tmpCol.A} {
switch rgba[cidx].(type) {
case int:
*vp = uint8(rgba[cidx].(int))
case float64:
*vp = uint8(rgba[cidx].(float64))
}
}
textColor = tmpCol
textColor = attributes.RGBAToColor()
}
// Initialize fontsize
var fontsize float64 = 120
if v, ok := attributes["font_size"].(float64); ok {
fontsize = v
if attributes.FontSize != nil {
fontsize = *attributes.FontSize
}
border := 10
if v, ok := attributes["border"].(int); ok {
border = v
if attributes.Border != nil {
border = *attributes.Border
}
if strings.TrimSpace(attributes["text"].(string)) != "" {
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes["text"].(string)), fontsize, border, textColor); err != nil {
if strings.TrimSpace(attributes.Text) != "" {
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes.Text), fontsize, border, textColor); err != nil {
return errors.Wrap(err, "Unable to render text")
}
}
if caption, ok := attributes["caption"].(string); ok && strings.TrimSpace(caption) != "" {
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(caption)); err != nil {
if strings.TrimSpace(attributes.Caption) != "" {
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(attributes.Caption)); err != nil {
return errors.Wrap(err, "Unable to render caption")
}
}
@ -86,4 +75,4 @@ func (d displayElementText) Display(ctx context.Context, idx int, attributes map
return errors.Wrap(sd.FillImage(idx, imgRenderer.GetImage()), "Unable to set image")
}
func (d displayElementText) NeedsLoop(attributes map[string]interface{}) bool { return false }
func (d displayElementText) NeedsLoop(attributes attributeCollection) bool { return false }

View file

@ -11,21 +11,21 @@ import (
const errorDisplayElementType = "color"
var errorDisplayElementAttributes = map[string]interface{}{
"rgba": []interface{}{0xff, 0x0, 0x0, 0xff},
var errorDisplayElementAttributes = attributeCollection{
RGBA: []int{0xff, 0x0, 0x0, 0xff},
}
type action interface {
Execute(attributes map[string]interface{}) error
Execute(attributes attributeCollection) error
}
type displayElement interface {
Display(ctx context.Context, idx int, attributes map[string]interface{}) error
Display(ctx context.Context, idx int, attributes attributeCollection) error
}
type refreshingDisplayElement interface {
NeedsLoop(attributes map[string]interface{}) bool
StartLoopDisplay(ctx context.Context, idx int, attributes map[string]interface{}) error
NeedsLoop(attributes attributeCollection) bool
StartLoopDisplay(ctx context.Context, idx int, attributes attributeCollection) error
StopLoopDisplay() error
}

View file

@ -7,15 +7,15 @@ func applySystemPages(conf *config) {
blankPage.Keys[i] = keyDefinition{
Display: dynamicElement{
Type: "color",
Attributes: map[string]interface{}{
"rgba": []interface{}{0x0, 0x0, 0x0, 0xff},
Attributes: attributeCollection{
RGBA: []int{0x0, 0x0, 0x0, 0xff},
},
},
Actions: []dynamicElement{
{
Type: "page",
Attributes: map[string]interface{}{
"relative": 1,
Attributes: attributeCollection{
Relative: 1,
},
},
},