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{}
|
||||
|
||||
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
|
||||
for k, v := range attributes.Env {
|
||||
processEnv[k] = v
|
||||
}
|
||||
|
||||
processEnv[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
if kc, ok := uinputKeyMapping[k]; ok {
|
||||
execCodes = append(execCodes, kc)
|
||||
} else {
|
||||
return errors.Errorf("Unknown key %q", kv)
|
||||
}
|
||||
} 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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
for k, v := range attributes.Env {
|
||||
processEnv[k] = v
|
||||
}
|
||||
|
||||
processEnv[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue