package main import ( "context" "encoding/json" "fmt" "log" "os" "time" "macvolumecontrol/logging" "macvolumecontrol/volume" "code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk" sdcontext "code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk/context" ) var _currentSettings *volume.VolumeSettings = &volume.VolumeSettings{} func main() { logging.Enable() log.Println("Starting plugin") params, err := streamdeck.ParseRegistrationParams(os.Args) if err != nil { log.Fatalf("error parsing registration params: %v", err) } sd := streamdeck.NewClient(context.Background(), params) defer sd.Close() setup(sd) if err := sd.Run(); err != nil { log.Fatalf("error running streamdeck client: %v\n", err) } } func setup(client *streamdeck.Client) { log.Println("Registering actions") action := client.Action("com.dlprows.macvolumecontrol.dialaction") contexts := make(map[string]struct{}) action.RegisterHandler(streamdeck.DialRotate, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error { log.Println("dial rotate") p := streamdeck.DialRotatePayload[any]{} if err := json.Unmarshal(event.Payload, &p); err != nil { return err } //volume.ChangeVolumeWithKeyboard(p.Ticks) newSettings, err := volume.ChangeVolume(p.Ticks) if err != nil { return err } return setFeedbackIfNeeded(ctx, client, newSettings) }) action.RegisterHandler(streamdeck.DialDown, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error { log.Println("dial down") newSettings, err := volume.ToggleMute() if err != nil { return err } return setFeedbackIfNeeded(ctx, client, newSettings) }) action.RegisterHandler(streamdeck.TouchTap, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error { log.Println("touch tap") newSettings, err := volume.ToggleMute() if err != nil { return err } return setFeedbackIfNeeded(ctx, client, newSettings) }) action.RegisterHandler(streamdeck.WillAppear, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error { log.Println("Will Appear") contexts[event.Context] = struct{}{} newSettings, err := volume.GetVolumeSettings() if err != nil { return err } return setFeedbackIfNeeded(ctx, client, newSettings) }) action.RegisterHandler(streamdeck.WillDisappear, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error { log.Println("Will Disappear") delete(contexts, event.Context) return nil }) //start background thread to keep the display up to date if changed outside the stream deck go func() { for range time.Tick(time.Second * 1) { newSettings, err := volume.GetVolumeSettings() if err != nil { log.Fatal(err) } for ctxStr := range contexts { //for each context //build a new context that can be used to perform outbound requests ctx := context.Background() ctx = sdcontext.WithContext(ctx, ctxStr) setFeedbackIfNeeded(ctx, client, newSettings) } } }() } func setFeedbackIfNeeded(ctx context.Context, client *streamdeck.Client, newSettings *volume.VolumeSettings) error { if _currentSettings.OutputVolume == newSettings.OutputVolume && _currentSettings.OutputMuted == newSettings.OutputMuted { return nil } payload := FeedbackPayload{} opacity := 1.0 if newSettings.OutputMuted { opacity = 0.5 } payload.Value = ValueWithOpacity[string]{ fmt.Sprintf("%d%%", newSettings.OutputVolume), opacity, } payload.Indicator = ValueWithOpacity[int]{ newSettings.OutputVolume, opacity, } payload.Icon = ValueWithOpacity[any]{nil, opacity} _currentSettings = newSettings return client.SetFeedback(ctx, payload) } type FeedbackPayload struct { Value ValueWithOpacity[string] `json:"value"` Indicator ValueWithOpacity[int] `json:"indicator"` Icon ValueWithOpacity[any] `json:"icon"` } type ValueWithOpacity[T any] struct { Value T `json:"value,omitempty"` Opacity float64 `json:"opacity"` }