From 2fd1279eb39868608c92e8fefea8f2a638e7c7a9 Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Sun, 9 Aug 2020 15:44:34 +0200 Subject: [PATCH] Handle mute and absent devices Signed-off-by: Knut Ahlers --- cmd/streamdeck/display_pulsevolume.go | 30 ++++++++++++++++++++------- cmd/streamdeck/pulseaudio.go | 26 ++++++++++++----------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/cmd/streamdeck/display_pulsevolume.go b/cmd/streamdeck/display_pulsevolume.go index 65970d8..a39ac5b 100644 --- a/cmd/streamdeck/display_pulsevolume.go +++ b/cmd/streamdeck/display_pulsevolume.go @@ -30,8 +30,10 @@ func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attribu sinkInputOK = sinkInputOK && sinkInputMatch != "" var ( - err error - volume float64 + err error + mute bool + notPresent bool + volume float64 ) switch { @@ -40,21 +42,35 @@ func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attribu return errors.New("Exactly one of 'sink' and 'sink_input' must be specified") case sinkInputOK: - volume, err = pulseClient.GetSinkInputVolume(sinkInputMatch) + volume, mute, err = pulseClient.GetSinkInputVolume(sinkInputMatch) case sinkOK: - volume, err = pulseClient.GetSinkVolume(sinkMatch) + volume, mute, err = pulseClient.GetSinkVolume(sinkMatch) } - if err != nil { + if err == errPulseNoSuchDevice { + notPresent = true + } else if err != nil { return errors.Wrap(err, "Unable to get volume") } img := newTextOnImageRenderer() + var ( + text = fmt.Sprintf("%.0f%%", volume*100) + textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff} + ) + + if notPresent { + text = "--" + textColor = color.RGBA{0xff, 0x0, 0x0, 0x0} + } else if mute { + text = "M" + textColor = color.RGBA{0xff, 0x0, 0x0, 0x0} + } + // Initialize color - var textColor color.Color = color.RGBA{0xff, 0xff, 0xff, 0xff} if rgba, ok := attributes["color"].([]interface{}); ok { if len(rgba) != 4 { return errors.New("RGBA color definition needs 4 hex values") @@ -85,7 +101,7 @@ func (d displayElementPulseVolume) Display(ctx context.Context, idx int, attribu border = v } - if err = img.DrawBigText(fmt.Sprintf("%.0f%%", volume*100), fontsize, border, textColor); err != nil { + if err = img.DrawBigText(text, fontsize, border, textColor); err != nil { return errors.Wrap(err, "Unable to draw text") } diff --git a/cmd/streamdeck/pulseaudio.go b/cmd/streamdeck/pulseaudio.go index 1c7986d..5bb7653 100644 --- a/cmd/streamdeck/pulseaudio.go +++ b/cmd/streamdeck/pulseaudio.go @@ -21,6 +21,8 @@ func init() { } } +var errPulseNoSuchDevice = errors.New("No such device") + type pulseAudioClient struct { client *pulse.Client } @@ -36,42 +38,42 @@ func newPulseAudioClient() (*pulseAudioClient, error) { func (p pulseAudioClient) Close() { p.client.Close() } -func (p pulseAudioClient) GetSinkInputVolume(match string) (float64, error) { +func (p pulseAudioClient) GetSinkInputVolume(match string) (float64, bool, error) { m, err := regexp.Compile(match) if err != nil { - return 0, errors.Wrap(err, "Unable to compile given match RegEx") + return 0, false, errors.Wrap(err, "Unable to compile given match RegEx") } var resp proto.GetSinkInputInfoListReply if err := p.client.RawRequest(&proto.GetSinkInputInfoList{}, &resp); err != nil { - return 0, errors.Wrap(err, "Unable to list sink inputs") + return 0, false, errors.Wrap(err, "Unable to list sink inputs") } for _, info := range resp { - if !m.MatchString(info.MediaName) && !m.Match(info.Properties["application.name"]) { + if !m.MatchString(info.MediaName) && !m.Match(info.Properties["application.name"]) || info.Corked { continue } sinkBase, err := p.getSinkBaseVolumeByIndex(info.SinkIndex) if err != nil { - return 0, errors.Wrap(err, "Unable to get sink base volume") + return 0, false, errors.Wrap(err, "Unable to get sink base volume") } - return p.unifyChannelVolumes(info.ChannelVolumes) / sinkBase, nil + return p.unifyChannelVolumes(info.ChannelVolumes) / sinkBase, info.Muted, nil } - return 0, errors.New("No such sink") + return 0, false, errPulseNoSuchDevice } -func (p pulseAudioClient) GetSinkVolume(match string) (float64, error) { +func (p pulseAudioClient) GetSinkVolume(match string) (float64, bool, error) { m, err := regexp.Compile(match) if err != nil { - return 0, errors.Wrap(err, "Unable to compile given match RegEx") + return 0, false, errors.Wrap(err, "Unable to compile given match RegEx") } var resp proto.GetSinkInfoListReply if err := p.client.RawRequest(&proto.GetSinkInfoList{}, &resp); err != nil { - return 0, errors.Wrap(err, "Unable to list sinks") + return 0, false, errors.Wrap(err, "Unable to list sinks") } for _, info := range resp { @@ -79,10 +81,10 @@ func (p pulseAudioClient) GetSinkVolume(match string) (float64, error) { continue } - return p.unifyChannelVolumes(info.ChannelVolumes) / float64(info.BaseVolume), nil + return p.unifyChannelVolumes(info.ChannelVolumes) / float64(info.BaseVolume), info.Mute, nil } - return 0, errors.New("No such sink") + return 0, false, errPulseNoSuchDevice } func (p pulseAudioClient) getSinkBaseVolumeByIndex(idx uint32) (float64, error) {