mirror of
https://github.com/Luzifer/streamdeck.git
synced 2024-12-20 17:51:21 +00:00
Move configuration to more stable format (#8)
This commit is contained in:
parent
393ba030a2
commit
ecdf4967ae
14 changed files with 207 additions and 295 deletions
|
@ -14,46 +14,25 @@ func init() {
|
||||||
|
|
||||||
type actionExec struct{}
|
type actionExec struct{}
|
||||||
|
|
||||||
func (actionExec) Execute(attributes map[string]interface{}) error {
|
func (actionExec) Execute(attributes attributeCollection) error {
|
||||||
cmd, ok := attributes["command"].([]interface{})
|
if attributes.Command == nil {
|
||||||
if !ok {
|
|
||||||
return errors.New("No command supplied")
|
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())
|
processEnv := env.ListToMap(os.Environ())
|
||||||
|
|
||||||
if e, ok := attributes["env"].(map[interface{}]interface{}); ok {
|
for k, v := range attributes.Env {
|
||||||
for k, v := range e {
|
processEnv[k] = v
|
||||||
key, ok := k.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processEnv[key] = value
|
command := exec.Command(attributes.Command[0], attributes.Command[1:]...)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
command := exec.Command(args[0], args[1:]...)
|
|
||||||
command.Env = env.MapToList(processEnv)
|
command.Env = env.MapToList(processEnv)
|
||||||
|
|
||||||
if v, ok := attributes["attach_stdout"].(bool); ok && v {
|
if attributes.AttachStdout {
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := attributes["attach_stderr"].(bool); ok && v {
|
if attributes.AttachStderr {
|
||||||
command.Stdout = os.Stderr
|
command.Stdout = os.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +40,8 @@ func (actionExec) Execute(attributes map[string]interface{}) error {
|
||||||
return errors.Wrap(err, "Unable to start command")
|
return errors.Wrap(err, "Unable to start command")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If "wait" is set and set to true start command and wait for execution
|
// If "wait" is set to true start command and wait for execution
|
||||||
if v, ok := attributes["wait"].(bool); ok && v {
|
if attributes.Wait {
|
||||||
return errors.Wrap(command.Wait(), "Command was not successful")
|
return errors.Wrap(command.Wait(), "Command was not successful")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -14,71 +13,53 @@ func init() {
|
||||||
|
|
||||||
type actionKeyPress struct{}
|
type actionKeyPress struct{}
|
||||||
|
|
||||||
func (actionKeyPress) Execute(attributes map[string]interface{}) error {
|
func (actionKeyPress) Execute(attributes attributeCollection) error {
|
||||||
var (
|
var (
|
||||||
delay time.Duration
|
|
||||||
execCodes []uint16
|
execCodes []uint16
|
||||||
ok bool
|
|
||||||
|
|
||||||
keyNames []interface{}
|
keyNames []string
|
||||||
keyCodes []interface{}
|
keyCodes []int
|
||||||
useKeyNames bool
|
useKeyNames bool
|
||||||
)
|
)
|
||||||
|
|
||||||
keyCodes, ok = attributes["key_codes"].([]interface{})
|
keyCodes = attributes.KeyCodes
|
||||||
if !ok {
|
if keyCodes == nil {
|
||||||
keyNames, ok = attributes["keys"].([]interface{})
|
keyNames = attributes.Keys
|
||||||
if !ok {
|
if keyNames == nil {
|
||||||
return errors.New("No key_codes or keys array present")
|
return errors.New("No key_codes or keys array present")
|
||||||
}
|
}
|
||||||
useKeyNames = true
|
useKeyNames = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := attributes["delay"].(string); ok {
|
|
||||||
if d, err := time.ParseDuration(v); err == nil {
|
|
||||||
delay = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if useKeyNames {
|
if useKeyNames {
|
||||||
for _, k := range keyNames {
|
for _, k := range keyNames {
|
||||||
// Convert misdetections into strings
|
if kc, ok := uinputKeyMapping[k]; ok {
|
||||||
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)
|
execCodes = append(execCodes, kc)
|
||||||
} else {
|
} else {
|
||||||
return errors.Errorf("Unknown key %q", kv)
|
return errors.Errorf("Unknown key %q", k)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("Unknown key type detected")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, k := range keyCodes {
|
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 {
|
if err := kbd.KeyDown(uinput.KeyLeftShift); err != nil {
|
||||||
return errors.Wrap(err, "Unable to set shift")
|
return errors.Wrap(err, "Unable to set shift")
|
||||||
}
|
}
|
||||||
defer kbd.KeyUp(uinput.KeyLeftShift)
|
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 {
|
if err := kbd.KeyDown(uinput.KeyLeftAlt); err != nil {
|
||||||
return errors.Wrap(err, "Unable to set shift")
|
return errors.Wrap(err, "Unable to set shift")
|
||||||
}
|
}
|
||||||
defer kbd.KeyUp(uinput.KeyLeftAlt)
|
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 {
|
if err := kbd.KeyDown(uinput.KeyLeftCtrl); err != nil {
|
||||||
return errors.Wrap(err, "Unable to set shift")
|
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 {
|
if err := kbd.KeyPress(kc); err != nil {
|
||||||
return errors.Wrap(err, "Unable to press key")
|
return errors.Wrap(err, "Unable to press key")
|
||||||
}
|
}
|
||||||
time.Sleep(delay)
|
time.Sleep(attributes.Delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -12,15 +12,12 @@ func init() {
|
||||||
|
|
||||||
type actionPage struct{}
|
type actionPage struct{}
|
||||||
|
|
||||||
func (actionPage) Execute(attributes map[string]interface{}) error {
|
func (actionPage) Execute(attributes attributeCollection) error {
|
||||||
name, nameOk := attributes["name"].(string)
|
if attributes.Name != "" {
|
||||||
relative, relativeOk := attributes["relative"].(int)
|
return errors.Wrap(togglePage(attributes.Name), "switch page")
|
||||||
|
|
||||||
if nameOk && name != "" {
|
|
||||||
return errors.Wrap(togglePage(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]
|
nextPage := pageStack[absRel]
|
||||||
pageStack = pageStack[absRel+1:]
|
pageStack = pageStack[absRel+1:]
|
||||||
return errors.Wrap(togglePage(nextPage), "switch relative page")
|
return errors.Wrap(togglePage(nextPage), "switch relative page")
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,86 +12,56 @@ func init() {
|
||||||
|
|
||||||
type actionPulseVolume struct{}
|
type actionPulseVolume struct{}
|
||||||
|
|
||||||
func (actionPulseVolume) Execute(attributes map[string]interface{}) error {
|
func (actionPulseVolume) Execute(attributes attributeCollection) error {
|
||||||
if pulseClient == nil {
|
if pulseClient == nil {
|
||||||
return errors.New("PulseAudio client not initialized")
|
return errors.New("PulseAudio client not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
devType, ok := attributes["device"].(string)
|
if attributes.Device == "" {
|
||||||
if !ok {
|
|
||||||
return errors.New("Missing 'device' attribute")
|
return errors.New("Missing 'device' attribute")
|
||||||
}
|
}
|
||||||
|
|
||||||
match, ok := attributes["match"].(string)
|
if attributes.Match == "" {
|
||||||
if !ok {
|
|
||||||
return errors.New("Missing 'match' attribute")
|
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
|
// Read volume
|
||||||
var (
|
var (
|
||||||
volAbs bool
|
volAbs bool
|
||||||
volVal float64
|
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) {
|
if attributes.SetVolume != nil {
|
||||||
case float64:
|
volVal = *attributes.SetVolume / 100
|
||||||
volVal = val.(float64) / 100
|
volAbs = true
|
||||||
|
} else if attributes.ChangeVolume != nil {
|
||||||
case int:
|
volVal = *attributes.ChangeVolume / 100
|
||||||
volVal = float64(val.(int)) / 100
|
volAbs = false
|
||||||
|
|
||||||
case int64:
|
|
||||||
volVal = float64(val.(int64)) / 100
|
|
||||||
}
|
|
||||||
|
|
||||||
volAbs = abs
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute change
|
// Execute change
|
||||||
switch devType {
|
switch attributes.Device {
|
||||||
|
|
||||||
case "input":
|
case "input":
|
||||||
return errors.Wrap(
|
return errors.Wrap(
|
||||||
pulseClient.SetSinkInputVolume(match, mute, volVal, volAbs),
|
pulseClient.SetSinkInputVolume(attributes.Match, attributes.Mute, volVal, volAbs),
|
||||||
"Unable to set sink input volume",
|
"Unable to set sink input volume",
|
||||||
)
|
)
|
||||||
|
|
||||||
case "sink":
|
case "sink":
|
||||||
return errors.Wrap(
|
return errors.Wrap(
|
||||||
pulseClient.SetSinkVolume(match, mute, volVal, volAbs),
|
pulseClient.SetSinkVolume(attributes.Match, attributes.Mute, volVal, volAbs),
|
||||||
"Unable to set sink volume",
|
"Unable to set sink volume",
|
||||||
)
|
)
|
||||||
|
|
||||||
case "source":
|
case "source":
|
||||||
return errors.Wrap(
|
return errors.Wrap(
|
||||||
pulseClient.SetSourceVolume(match, mute, volVal, volAbs),
|
pulseClient.SetSourceVolume(attributes.Match, attributes.Mute, volVal, volAbs),
|
||||||
"Unable to set source volume",
|
"Unable to set source volume",
|
||||||
)
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("Unsupported device type: %q", devType)
|
return errors.Errorf("Unsupported device type: %q", attributes.Device)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ func init() {
|
||||||
|
|
||||||
type actionReloadConfig struct{}
|
type actionReloadConfig struct{}
|
||||||
|
|
||||||
func (actionReloadConfig) Execute(attributes map[string]interface{}) error {
|
func (actionReloadConfig) Execute(attributes attributeCollection) error {
|
||||||
if err := loadConfig(); err != nil {
|
if err := loadConfig(); err != nil {
|
||||||
return errors.Wrap(err, "Unable to reload config")
|
return errors.Wrap(err, "Unable to reload config")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ var actionToggleDisplayPreviousBrightness int
|
||||||
|
|
||||||
type actionToggleDisplay struct{}
|
type actionToggleDisplay struct{}
|
||||||
|
|
||||||
func (actionToggleDisplay) Execute(attributes map[string]interface{}) error {
|
func (actionToggleDisplay) Execute(attributes attributeCollection) error {
|
||||||
var newB int
|
var newB int
|
||||||
if currentBrightness > 0 {
|
if currentBrightness > 0 {
|
||||||
actionToggleDisplayPreviousBrightness = currentBrightness
|
actionToggleDisplayPreviousBrightness = currentBrightness
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"image/color"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -10,36 +13,90 @@ import (
|
||||||
|
|
||||||
const defaultLongPressDuration = 500 * time.Millisecond
|
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 {
|
type config struct {
|
||||||
AutoReload bool `yaml:"auto_reload"`
|
AutoReload bool `json:"auto_reload" yaml:"auto_reload"`
|
||||||
CaptionBorder int `yaml:"caption_border"`
|
CaptionBorder int `json:"caption_border" yaml:"caption_border"`
|
||||||
CaptionColor [4]int `yaml:"caption_color"`
|
CaptionColor [4]int `json:"caption_color" yaml:"caption_color"`
|
||||||
CaptionFont string `yaml:"caption_font"`
|
CaptionFont string `json:"caption_font" yaml:"caption_font"`
|
||||||
CaptionFontSize float64 `yaml:"caption_font_size"`
|
CaptionFontSize float64 `json:"caption_font_size" yaml:"caption_font_size"`
|
||||||
CaptionPosition captionPosition `yaml:"caption_position"`
|
CaptionPosition captionPosition `json:"caption_position" yaml:"caption_position"`
|
||||||
DefaultBrightness int `yaml:"default_brightness"`
|
DefaultBrightness int `json:"default_brightness" yaml:"default_brightness"`
|
||||||
DefaultPage string `yaml:"default_page"`
|
DefaultPage string `json:"default_page" yaml:"default_page"`
|
||||||
DisplayOffTime time.Duration `yaml:"display_off_time"`
|
DisplayOffTime time.Duration `json:"display_off_time" yaml:"display_off_time"`
|
||||||
LongPressDuration time.Duration `yaml:"long_press_duration"`
|
LongPressDuration time.Duration `json:"long_press_duration" yaml:"long_press_duration"`
|
||||||
Pages map[string]page `yaml:"pages"`
|
Pages map[string]page `json:"pages" yaml:"pages"`
|
||||||
RenderFont string `yaml:"render_font"`
|
RenderFont string `json:"render_font" yaml:"render_font"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type page struct {
|
type page struct {
|
||||||
Keys map[int]keyDefinition `yaml:"keys"`
|
Keys map[int]keyDefinition `json:"keys" yaml:"keys"`
|
||||||
Overlay string `yaml:"overlay"`
|
Overlay string `json:"overlay" yaml:"overlay"`
|
||||||
Underlay string `yaml:"underlay"`
|
Underlay string `json:"underlay" yaml:"underlay"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyDefinition struct {
|
type keyDefinition struct {
|
||||||
Display dynamicElement `yaml:"display"`
|
Display dynamicElement `json:"display" yaml:"display"`
|
||||||
Actions []dynamicElement `yaml:"actions"`
|
Actions []dynamicElement `json:"actions" yaml:"actions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type dynamicElement struct {
|
type dynamicElement struct {
|
||||||
Type string `yaml:"type"`
|
Type string `json:"type" yaml:"type"`
|
||||||
LongPress bool `yaml:"long_press"`
|
LongPress bool `json:"long_press" yaml:"long_press"`
|
||||||
Attributes map[string]interface{} `yaml:"attributes"`
|
Attributes attributeCollection `json:"attributes" yaml:"attributes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfig() config {
|
func newConfig() config {
|
||||||
|
|
|
@ -13,13 +13,13 @@ func init() {
|
||||||
|
|
||||||
type displayElementColor struct{}
|
type displayElementColor struct{}
|
||||||
|
|
||||||
func (d displayElementColor) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
|
func (d displayElementColor) Display(ctx context.Context, idx int, attributes attributeCollection) error {
|
||||||
if name, ok := attributes["color"].(string); ok {
|
if attributes.Color != "" {
|
||||||
return d.displayPredefinedColor(idx, name)
|
return d.displayPredefinedColor(idx, attributes.Color)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rgba, ok := attributes["rgba"].([]interface{}); ok {
|
if attributes.RGBA != nil {
|
||||||
if len(rgba) != 4 {
|
if len(attributes.RGBA) != 4 {
|
||||||
return errors.New("RGBA color definition needs 4 hex values")
|
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 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")
|
return errors.New("No valid attributes specified for type color")
|
||||||
|
|
|
@ -25,48 +25,27 @@ type displayElementExec struct {
|
||||||
running bool
|
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 (
|
var (
|
||||||
err error
|
err error
|
||||||
imgRenderer = newTextOnImageRenderer()
|
imgRenderer = newTextOnImageRenderer()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize command
|
// Initialize command
|
||||||
cmd, ok := attributes["command"].([]interface{})
|
if attributes.Command == nil {
|
||||||
if !ok {
|
|
||||||
return errors.New("No command supplied")
|
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
|
// Execute command and parse it
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
processEnv := env.ListToMap(os.Environ())
|
processEnv := env.ListToMap(os.Environ())
|
||||||
|
|
||||||
if e, ok := attributes["env"].(map[interface{}]interface{}); ok {
|
for k, v := range attributes.Env {
|
||||||
for k, v := range e {
|
processEnv[k] = v
|
||||||
key, ok := k.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processEnv[key] = value
|
command := exec.Command(attributes.Command[0], attributes.Command[1:]...)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
command := exec.Command(args[0], args[1:]...)
|
|
||||||
command.Env = env.MapToList(processEnv)
|
command.Env = env.MapToList(processEnv)
|
||||||
command.Stdout = buf
|
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")
|
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 {
|
if err = json.Unmarshal(buf.Bytes(), &tmpAttrs); err == nil {
|
||||||
// Reset text to empty as it was parsable json
|
// Reset text to empty as it was parsable json
|
||||||
attributes["text"] = ""
|
attributes = tmpAttrs
|
||||||
|
|
||||||
for k, v := range tmpAttrs {
|
|
||||||
attributes[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize background
|
// Initialize background
|
||||||
if filename, ok := attributes["image"].(string); ok {
|
if attributes.Image != "" {
|
||||||
if err = imgRenderer.DrawBackgroundFromFile(filename); err != nil {
|
if err = imgRenderer.DrawBackgroundFromFile(attributes.Image); err != nil {
|
||||||
return errors.Wrap(err, "Unable to draw background from disk")
|
return errors.Wrap(err, "Unable to draw background from disk")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize color
|
// Initialize color
|
||||||
var textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
var textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
if rgba, ok := attributes["color"].([]interface{}); ok {
|
if attributes.RGBA != nil {
|
||||||
if len(rgba) != 4 {
|
if len(attributes.RGBA) != 4 {
|
||||||
return errors.New("RGBA color definition needs 4 hex values")
|
return errors.New("RGBA color definition needs 4 hex values")
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpCol := color.RGBA{}
|
textColor = attributes.RGBAToColor()
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize fontsize
|
// Initialize fontsize
|
||||||
var fontsize float64 = 120
|
var fontsize float64 = 120
|
||||||
if v, ok := attributes["font_size"].(float64); ok {
|
if attributes.FontSize != nil {
|
||||||
fontsize = v
|
fontsize = *attributes.FontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
border := 10
|
border := 10
|
||||||
if v, ok := attributes["border"].(int); ok {
|
if attributes.Border != nil {
|
||||||
border = v
|
border = *attributes.Border
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(attributes["text"].(string)) != "" {
|
if strings.TrimSpace(attributes.Text) != "" {
|
||||||
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes["text"].(string)), fontsize, border, textColor); err != nil {
|
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes.Text), fontsize, border, textColor); err != nil {
|
||||||
return errors.Wrap(err, "Unable to render text")
|
return errors.Wrap(err, "Unable to render text")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if caption, ok := attributes["caption"].(string); ok && strings.TrimSpace(caption) != "" {
|
if strings.TrimSpace(attributes.Caption) != "" {
|
||||||
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(caption)); err != nil {
|
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(attributes.Caption)); err != nil {
|
||||||
return errors.Wrap(err, "Unable to render caption")
|
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")
|
return errors.Wrap(sd.FillImage(idx, imgRenderer.GetImage()), "Unable to set image")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d displayElementExec) NeedsLoop(attributes map[string]interface{}) bool {
|
func (d displayElementExec) NeedsLoop(attributes attributeCollection) bool {
|
||||||
if v, ok := attributes["interval"].(int); ok {
|
return attributes.Interval > 0
|
||||||
return v > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
d.running = true
|
||||||
|
|
||||||
interval := 5 * time.Second
|
|
||||||
if v, ok := attributes["interval"].(int); ok {
|
|
||||||
interval = time.Duration(v) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for tick := time.NewTicker(interval); ; <-tick.C {
|
for tick := time.NewTicker(attributes.Interval); ; <-tick.C {
|
||||||
if !d.running {
|
if !d.running {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,22 +21,21 @@ func init() {
|
||||||
|
|
||||||
type displayElementImage struct{}
|
type displayElementImage struct{}
|
||||||
|
|
||||||
func (d displayElementImage) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
|
func (d displayElementImage) Display(ctx context.Context, idx int, attributes attributeCollection) error {
|
||||||
filename, ok := attributes["path"].(string)
|
filename := attributes.Path
|
||||||
if !ok {
|
if filename == "" {
|
||||||
url, ok := attributes["url"].(string)
|
if attributes.URL == "" {
|
||||||
if !ok {
|
|
||||||
return errors.New("No path or url attribute specified")
|
return errors.New("No path or url attribute specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
filename, err = d.getCacheFileName(url)
|
filename, err = d.getCacheFileName(attributes.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Unable to get cache filename for image url")
|
return errors.Wrap(err, "Unable to get cache filename for image url")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(attributes.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Unable to request image url")
|
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")
|
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")
|
return "", errors.Wrap(err, "Unable to create cache dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,18 +19,16 @@ func init() {
|
||||||
|
|
||||||
type displayElementPulseVolume struct{}
|
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 {
|
if pulseClient == nil {
|
||||||
return errors.New("PulseAudio client not initialized")
|
return errors.New("PulseAudio client not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
devType, ok := attributes["device"].(string)
|
if attributes.Device == "" {
|
||||||
if !ok {
|
|
||||||
return errors.New("Missing 'device' attribute")
|
return errors.New("Missing 'device' attribute")
|
||||||
}
|
}
|
||||||
|
|
||||||
match, ok := attributes["match"].(string)
|
if attributes.Match == "" {
|
||||||
if !ok {
|
|
||||||
return errors.New("Missing 'match' attribute")
|
return errors.New("Missing 'match' attribute")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,19 +39,19 @@ func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attribu
|
||||||
volume float64
|
volume float64
|
||||||
)
|
)
|
||||||
|
|
||||||
switch devType {
|
switch attributes.Device {
|
||||||
|
|
||||||
case "input":
|
case "input":
|
||||||
volume, mute, _, _, err = pulseClient.GetSinkInputVolume(match)
|
volume, mute, _, _, err = pulseClient.GetSinkInputVolume(attributes.Match)
|
||||||
|
|
||||||
case "sink":
|
case "sink":
|
||||||
volume, mute, _, _, err = pulseClient.GetSinkVolume(match)
|
volume, mute, _, _, err = pulseClient.GetSinkVolume(attributes.Match)
|
||||||
|
|
||||||
case "source":
|
case "source":
|
||||||
volume, mute, _, _, err = pulseClient.GetSourceVolume(match)
|
volume, mute, _, _, err = pulseClient.GetSourceVolume(attributes.Match)
|
||||||
|
|
||||||
default:
|
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
|
// Initialize color
|
||||||
if rgba, ok := attributes["color"].([]interface{}); ok {
|
if attributes.RGBA != nil {
|
||||||
if len(rgba) != 4 {
|
if len(attributes.RGBA) != 4 {
|
||||||
return errors.New("RGBA color definition needs 4 hex values")
|
return errors.New("RGBA color definition needs 4 hex values")
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpCol := color.RGBA{}
|
textColor = attributes.RGBAToColor()
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize fontsize
|
// Initialize fontsize
|
||||||
var fontsize float64 = 120
|
var fontsize float64 = 120
|
||||||
if v, ok := attributes["font_size"].(float64); ok {
|
if attributes.FontSize != nil {
|
||||||
fontsize = v
|
fontsize = *attributes.FontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
var border = 10
|
border := 10
|
||||||
if v, ok := attributes["border"].(int); ok {
|
if attributes.Border != nil {
|
||||||
border = v
|
border = *attributes.Border
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = img.DrawBigText(text, fontsize, border, textColor); err != nil {
|
if err = img.DrawBigText(text, fontsize, border, textColor); err != nil {
|
||||||
return errors.Wrap(err, "Unable to draw text")
|
return errors.Wrap(err, "Unable to draw text")
|
||||||
}
|
}
|
||||||
|
|
||||||
if caption, ok := attributes["caption"].(string); ok && strings.TrimSpace(caption) != "" {
|
if strings.TrimSpace(attributes.Caption) != "" {
|
||||||
if err = img.DrawCaptionText(strings.TrimSpace(caption)); err != nil {
|
if err = img.DrawCaptionText(strings.TrimSpace(attributes.Caption)); err != nil {
|
||||||
return errors.Wrap(err, "Unable to render caption")
|
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")
|
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 {
|
func (d *displayElementPulseVolume) StartLoopDisplay(ctx context.Context, idx int, attributes attributeCollection) error {
|
||||||
var interval = time.Second
|
interval := time.Second
|
||||||
if v, ok := attributes["interval"].(int); ok {
|
if attributes.Interval > 0 {
|
||||||
interval = time.Duration(v) * time.Second
|
interval = attributes.Interval
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -17,59 +17,48 @@ type displayElementText struct {
|
||||||
running bool
|
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 (
|
var (
|
||||||
err error
|
err error
|
||||||
imgRenderer = newTextOnImageRenderer()
|
imgRenderer = newTextOnImageRenderer()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize background
|
// Initialize background
|
||||||
if filename, ok := attributes["image"].(string); ok {
|
if attributes.Image != "" {
|
||||||
if err = imgRenderer.DrawBackgroundFromFile(filename); err != nil {
|
if err = imgRenderer.DrawBackgroundFromFile(attributes.Image); err != nil {
|
||||||
return errors.Wrap(err, "Unable to draw background from disk")
|
return errors.Wrap(err, "Unable to draw background from disk")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize color
|
// Initialize color
|
||||||
var textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
var textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
if rgba, ok := attributes["color"].([]interface{}); ok {
|
if attributes.RGBA != nil {
|
||||||
if len(rgba) != 4 {
|
if len(attributes.RGBA) != 4 {
|
||||||
return errors.New("RGBA color definition needs 4 hex values")
|
return errors.New("RGBA color definition needs 4 hex values")
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpCol := color.RGBA{}
|
textColor = attributes.RGBAToColor()
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize fontsize
|
// Initialize fontsize
|
||||||
var fontsize float64 = 120
|
var fontsize float64 = 120
|
||||||
if v, ok := attributes["font_size"].(float64); ok {
|
if attributes.FontSize != nil {
|
||||||
fontsize = v
|
fontsize = *attributes.FontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
border := 10
|
border := 10
|
||||||
if v, ok := attributes["border"].(int); ok {
|
if attributes.Border != nil {
|
||||||
border = v
|
border = *attributes.Border
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(attributes["text"].(string)) != "" {
|
if strings.TrimSpace(attributes.Text) != "" {
|
||||||
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes["text"].(string)), fontsize, border, textColor); err != nil {
|
if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes.Text), fontsize, border, textColor); err != nil {
|
||||||
return errors.Wrap(err, "Unable to render text")
|
return errors.Wrap(err, "Unable to render text")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if caption, ok := attributes["caption"].(string); ok && strings.TrimSpace(caption) != "" {
|
if strings.TrimSpace(attributes.Caption) != "" {
|
||||||
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(caption)); err != nil {
|
if err = imgRenderer.DrawCaptionText(strings.TrimSpace(attributes.Caption)); err != nil {
|
||||||
return errors.Wrap(err, "Unable to render caption")
|
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")
|
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 }
|
||||||
|
|
|
@ -11,21 +11,21 @@ import (
|
||||||
|
|
||||||
const errorDisplayElementType = "color"
|
const errorDisplayElementType = "color"
|
||||||
|
|
||||||
var errorDisplayElementAttributes = map[string]interface{}{
|
var errorDisplayElementAttributes = attributeCollection{
|
||||||
"rgba": []interface{}{0xff, 0x0, 0x0, 0xff},
|
RGBA: []int{0xff, 0x0, 0x0, 0xff},
|
||||||
}
|
}
|
||||||
|
|
||||||
type action interface {
|
type action interface {
|
||||||
Execute(attributes map[string]interface{}) error
|
Execute(attributes attributeCollection) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type displayElement interface {
|
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 {
|
type refreshingDisplayElement interface {
|
||||||
NeedsLoop(attributes map[string]interface{}) bool
|
NeedsLoop(attributes attributeCollection) bool
|
||||||
StartLoopDisplay(ctx context.Context, idx int, attributes map[string]interface{}) error
|
StartLoopDisplay(ctx context.Context, idx int, attributes attributeCollection) error
|
||||||
StopLoopDisplay() error
|
StopLoopDisplay() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,15 +7,15 @@ func applySystemPages(conf *config) {
|
||||||
blankPage.Keys[i] = keyDefinition{
|
blankPage.Keys[i] = keyDefinition{
|
||||||
Display: dynamicElement{
|
Display: dynamicElement{
|
||||||
Type: "color",
|
Type: "color",
|
||||||
Attributes: map[string]interface{}{
|
Attributes: attributeCollection{
|
||||||
"rgba": []interface{}{0x0, 0x0, 0x0, 0xff},
|
RGBA: []int{0x0, 0x0, 0x0, 0xff},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []dynamicElement{
|
Actions: []dynamicElement{
|
||||||
{
|
{
|
||||||
Type: "page",
|
Type: "page",
|
||||||
Attributes: map[string]interface{}{
|
Attributes: attributeCollection{
|
||||||
"relative": 1,
|
Relative: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue