-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b90b86d
commit 2abd9e7
Showing
4 changed files
with
440 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package pipewire | ||
|
||
import ( | ||
"fmt" | ||
"os/exec" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
func pwLink(outPortID, inPortID pwObjectID) error { | ||
cmd := exec.Command("pw-link", "-L", fmt.Sprint(outPortID), fmt.Sprint(inPortID)) | ||
if err := cmd.Run(); err != nil { | ||
var exitErr *exec.ExitError | ||
if errors.As(err, &exitErr) { | ||
return errors.Wrapf(err, "failed to run pw-link: %s", exitErr.Stderr) | ||
} | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package pipewire | ||
|
||
import ( | ||
"encoding/json" | ||
"os/exec" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
type pwObjectType string | ||
|
||
const ( | ||
pwInterfaceDevice pwObjectType = "PipeWire:Interface:Device" | ||
pwInterfaceNode pwObjectType = "PipeWire:Interface:Node" | ||
pwInterfacePort pwObjectType = "PipeWire:Interface:Port" | ||
pwInterfaceLink pwObjectType = "PipeWire:Interface:Link" | ||
) | ||
|
||
type pwDump []pwObject | ||
|
||
func pwObjects() (pwDump, error) { | ||
dumpOutput, err := exec.Command("pw-dump").Output() | ||
if err != nil { | ||
var execErr *exec.ExitError | ||
if errors.As(err, &execErr) { | ||
return nil, errors.Wrapf(err, "failed to run pw-dump: %s", execErr.Stderr) | ||
} | ||
return nil, errors.Wrap(err, "failed to run pw-dump") | ||
} | ||
|
||
var dump pwDump | ||
if err := json.Unmarshal(dumpOutput, &dump); err != nil { | ||
return nil, errors.Wrap(err, "failed to parse pw-dump output") | ||
} | ||
|
||
return dump, nil | ||
} | ||
|
||
func (d pwDump) Links() pwDump { | ||
return d. | ||
Filter(func(o pwObject) bool { return o.Type == pwInterfaceLink }) | ||
} | ||
|
||
func (d pwDump) AudioSinks() pwDump { | ||
return d.Filter( | ||
func(o pwObject) bool { return o.Type == pwInterfaceNode }, | ||
func(o pwObject) bool { return o.Info.Props.MediaClass == pwAudioSink }, | ||
) | ||
} | ||
|
||
// Filter filters for the devices that satisfies f. | ||
func (d pwDump) Filter(fns ...func(pwObject) bool) pwDump { | ||
filtered := make(pwDump, 0, len(d)) | ||
loop: | ||
for _, device := range d { | ||
for _, f := range fns { | ||
if !f(device) { | ||
continue loop | ||
} | ||
} | ||
filtered = append(filtered, device) | ||
} | ||
return filtered | ||
} | ||
|
||
// Find returns the first object that satisfies f. | ||
func (d pwDump) Find(f func(pwObject) bool) *pwObject { | ||
for i, device := range d { | ||
if f(device) { | ||
return &d[i] | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Object gets the object with the given ID. | ||
func (d pwDump) Object(id pwObjectID) *pwObject { | ||
for i, o := range d { | ||
if o.ID == id { | ||
return &d[i] | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// TODO: generate a unique ID for catnip, so we can easier look up this stuff. | ||
|
||
type pwPortDirection string | ||
|
||
const ( | ||
pwPortIn = "in" | ||
pwPortOut = "out" | ||
) | ||
|
||
// ResolvePorts returns all PipeWire port objects that belong to the given | ||
// object. | ||
func (d pwDump) ResolvePorts(object *pwObject, dir pwPortDirection) pwDump { | ||
return d.Filter( | ||
func(o pwObject) bool { return o.Type == pwInterfacePort }, | ||
func(o pwObject) bool { | ||
return o.Info.Props.NodeID == object.ID && o.Info.Props.PortDirection == dir | ||
}, | ||
) | ||
} | ||
|
||
// OutputLinks returns all links that are connected to the given object. | ||
func (d pwDump) OutputLinks(output *pwObject) pwDump { | ||
return d.Filter( | ||
func(o pwObject) bool { return o.Type == pwInterfaceLink }, | ||
func(o pwObject) bool { return o.Info.Props.LinkOutputNode == output.ID }, | ||
) | ||
} | ||
|
||
// InputLinks returns all links that are connected to the given object. | ||
func (d pwDump) InputLinks(output *pwObject) pwDump { | ||
return d.Filter( | ||
func(o pwObject) bool { return o.Type == pwInterfaceLink }, | ||
func(o pwObject) bool { return o.Info.Props.LinkInputNode == output.ID }, | ||
) | ||
} | ||
|
||
type pwObjectID int64 | ||
|
||
type pwObject struct { | ||
ID pwObjectID `json:"id"` | ||
Type pwObjectType `json:"type"` | ||
Info struct { | ||
Props pwInfoProps `json:"props"` | ||
} `json:"info"` | ||
} | ||
|
||
type pwInfoProps struct { | ||
pwDeviceProps | ||
pwNodeProps | ||
pwLinkProps | ||
pwPortProps | ||
MediaClass string `json:"media.class"` | ||
|
||
JSON json.RawMessage `json:"-"` | ||
} | ||
|
||
func (p *pwInfoProps) UnmarshalJSON(data []byte) error { | ||
type Alias pwInfoProps | ||
if err := json.Unmarshal(data, (*Alias)(p)); err != nil { | ||
return err | ||
} | ||
p.JSON = append([]byte(nil), data...) | ||
return nil | ||
} | ||
|
||
type pwDeviceProps struct { | ||
DeviceName string `json:"device.name"` | ||
} | ||
|
||
// pwNodeProps is for Audio/Sink only. | ||
type pwNodeProps struct { | ||
NodeName string `json:"node.name"` | ||
NodeNick string `json:"node.nick"` | ||
NodeDescription string `json:"node.description"` | ||
} | ||
|
||
// Constants for MediaClass. | ||
const ( | ||
pwAudioDevice string = "Audio/Device" | ||
pwAudioSink string = "Audio/Sink" | ||
) | ||
|
||
// pwLinkProps is for links only. NodeID is the object ID; PortID is the port | ||
// object's ID. Input would be our catnip. Output would be the sink. | ||
// | ||
// We don't actually need to resolve the PortID to remove it, but we do need to | ||
// find all relevant ports to create new links. We can do this by filtering for | ||
// all Ports with the same NodeID. | ||
type pwLinkProps struct { | ||
LinkOutputNode pwObjectID `json:"link.output.node"` | ||
LinkOutputPort pwObjectID `json:"link.output.port"` | ||
LinkInputNode pwObjectID `json:"link.input.node"` | ||
LinkInputPort pwObjectID `json:"link.input.port"` | ||
} | ||
|
||
type pwPortProps struct { | ||
PortID pwObjectID `json:"port.id"` | ||
PortName string `json:"port.name"` | ||
PortAlias string `json:"port.alias"` | ||
PortDirection pwPortDirection `json:"port.direction"` | ||
NodeID pwObjectID `json:"node.id"` | ||
ObjectPath string `json:"object.path"` | ||
} |
Oops, something went wrong.