github.com/noriah/catnip@v1.8.5/input/pipewire/dump.go (about) 1 package pipewire 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 "os/exec" 8 9 "github.com/pkg/errors" 10 ) 11 12 type pwObjects []pwObject 13 14 func pwDump(ctx context.Context) (pwObjects, error) { 15 cmd := exec.CommandContext(ctx, "pw-dump") 16 cmd.Stderr = os.Stderr 17 18 dumpOutput, err := cmd.Output() 19 if err != nil { 20 var execErr *exec.ExitError 21 if errors.As(err, &execErr) { 22 return nil, errors.Wrapf(err, "failed to run pw-dump: %s", execErr.Stderr) 23 } 24 return nil, errors.Wrap(err, "failed to run pw-dump") 25 } 26 27 var dump pwObjects 28 if err := json.Unmarshal(dumpOutput, &dump); err != nil { 29 return nil, errors.Wrap(err, "failed to parse pw-dump output") 30 } 31 32 return dump, nil 33 } 34 35 // Filter filters for the devices that satisfies f. 36 func (d pwObjects) Filter(fns ...func(pwObject) bool) pwObjects { 37 filtered := make(pwObjects, 0, len(d)) 38 loop: 39 for _, device := range d { 40 for _, f := range fns { 41 if !f(device) { 42 continue loop 43 } 44 } 45 filtered = append(filtered, device) 46 } 47 return filtered 48 } 49 50 // Find returns the first object that satisfies f. 51 func (d pwObjects) Find(f func(pwObject) bool) *pwObject { 52 for i, device := range d { 53 if f(device) { 54 return &d[i] 55 } 56 } 57 return nil 58 } 59 60 // ResolvePorts returns all PipeWire port objects that belong to the given 61 // object. 62 func (d pwObjects) ResolvePorts(object *pwObject, dir pwPortDirection) pwObjects { 63 return d.Filter( 64 func(o pwObject) bool { return o.Type == pwInterfacePort }, 65 func(o pwObject) bool { 66 return o.Info.Props.NodeID == object.ID && o.Info.Props.PortDirection == dir 67 }, 68 ) 69 } 70 71 type pwObjectID int64 72 73 type pwObjectType string 74 75 const ( 76 pwInterfaceDevice pwObjectType = "PipeWire:Interface:Device" 77 pwInterfaceNode pwObjectType = "PipeWire:Interface:Node" 78 pwInterfacePort pwObjectType = "PipeWire:Interface:Port" 79 pwInterfaceLink pwObjectType = "PipeWire:Interface:Link" 80 ) 81 82 type pwObject struct { 83 ID pwObjectID `json:"id"` 84 Type pwObjectType `json:"type"` 85 Info struct { 86 Props pwInfoProps `json:"props"` 87 } `json:"info"` 88 } 89 90 type pwInfoProps struct { 91 pwDeviceProps 92 pwNodeProps 93 pwPortProps 94 MediaClass string `json:"media.class"` 95 96 JSON json.RawMessage `json:"-"` 97 } 98 99 func (p *pwInfoProps) UnmarshalJSON(data []byte) error { 100 type Alias pwInfoProps 101 if err := json.Unmarshal(data, (*Alias)(p)); err != nil { 102 return err 103 } 104 p.JSON = append([]byte(nil), data...) 105 return nil 106 } 107 108 type pwDeviceProps struct { 109 DeviceName string `json:"device.name"` 110 } 111 112 // pwNodeProps is for Audio/Sink only. 113 type pwNodeProps struct { 114 NodeName string `json:"node.name"` 115 NodeNick string `json:"node.nick"` 116 NodeDescription string `json:"node.description"` 117 } 118 119 // Constants for MediaClass. 120 const ( 121 pwAudioDevice string = "Audio/Device" 122 pwAudioSink string = "Audio/Sink" 123 pwStreamOutputAudio string = "Stream/Output/Audio" 124 ) 125 126 type pwPortDirection string 127 128 const ( 129 pwPortIn = "in" 130 pwPortOut = "out" 131 ) 132 133 type pwPortProps struct { 134 PortID pwObjectID `json:"port.id"` 135 PortName string `json:"port.name"` 136 PortAlias string `json:"port.alias"` 137 PortDirection pwPortDirection `json:"port.direction"` 138 NodeID pwObjectID `json:"node.id"` 139 ObjectPath string `json:"object.path"` 140 }