github.com/noriah/catnip@v1.8.5/input/pipewire/link.go (about)

     1  package pipewire
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  func pwLink(outPortID, inPortID pwObjectID) error {
    16  	cmd := exec.Command("pw-link", "-L", fmt.Sprint(outPortID), fmt.Sprint(inPortID))
    17  	if err := cmd.Run(); err != nil {
    18  		var exitErr *exec.ExitError
    19  		if errors.As(err, &exitErr) && exitErr.Stderr != nil {
    20  			return errors.Wrapf(err, "failed to run pw-link: %s", exitErr.Stderr)
    21  		}
    22  		return err
    23  	}
    24  	return nil
    25  }
    26  
    27  type pwLinkObject struct {
    28  	DeviceName string
    29  	PortID     pwObjectID
    30  	PortName   string // usually like {input,output}_{FL,FR}
    31  }
    32  
    33  func pwLinkObjectParse(line string) (pwLinkObject, error) {
    34  	var obj pwLinkObject
    35  
    36  	idStr, portStr, ok := strings.Cut(line, " ")
    37  	if !ok {
    38  		return obj, fmt.Errorf("failed to parse pw-link object %q", line)
    39  	}
    40  
    41  	id, err := strconv.Atoi(idStr)
    42  	if err != nil {
    43  		return obj, errors.Wrapf(err, "failed to parse pw-link object id %q", idStr)
    44  	}
    45  
    46  	name, port, ok := strings.Cut(portStr, ":")
    47  	if !ok {
    48  		return obj, fmt.Errorf("failed to parse pw-link port string %q", portStr)
    49  	}
    50  
    51  	obj = pwLinkObject{
    52  		PortID:     pwObjectID(id),
    53  		DeviceName: name,
    54  		PortName:   port,
    55  	}
    56  
    57  	return obj, nil
    58  }
    59  
    60  type pwLinkType string
    61  
    62  const (
    63  	pwLinkInputPorts  pwLinkType = "i"
    64  	pwLinkOutputPorts pwLinkType = "o"
    65  )
    66  
    67  type pwLinkEvent interface {
    68  	pwLinkEvent()
    69  }
    70  
    71  type pwLinkAdd pwLinkObject
    72  type pwLinkRemove pwLinkObject
    73  
    74  func (pwLinkAdd) pwLinkEvent()    {}
    75  func (pwLinkRemove) pwLinkEvent() {}
    76  
    77  func pwLinkMonitor(ctx context.Context, typ pwLinkType, ch chan<- pwLinkEvent) error {
    78  	cmd := exec.CommandContext(ctx, "pw-link", "-mI"+string(typ))
    79  	cmd.Stderr = os.Stderr
    80  
    81  	o, err := cmd.StdoutPipe()
    82  	if err != nil {
    83  		return errors.Wrap(err, "failed to get stdout pipe")
    84  	}
    85  	defer o.Close()
    86  
    87  	if err := cmd.Start(); err != nil {
    88  		return errors.Wrap(err, "pw-link -m")
    89  	}
    90  
    91  	scanner := bufio.NewScanner(o)
    92  	for scanner.Scan() {
    93  		line := scanner.Text()
    94  		if line == "" {
    95  			continue
    96  		}
    97  
    98  		mark := line[0]
    99  
   100  		line = strings.TrimSpace(line[1:])
   101  
   102  		obj, err := pwLinkObjectParse(line)
   103  		if err != nil {
   104  			continue
   105  		}
   106  
   107  		var ev pwLinkEvent
   108  		switch mark {
   109  		case '=':
   110  			fallthrough
   111  		case '+':
   112  			ev = pwLinkAdd(obj)
   113  		case '-':
   114  			ev = pwLinkRemove(obj)
   115  		default:
   116  			continue
   117  		}
   118  
   119  		select {
   120  		case <-ctx.Done():
   121  			return ctx.Err()
   122  		case ch <- ev:
   123  		}
   124  	}
   125  
   126  	return errors.Wrap(cmd.Wait(), "pw-link exited")
   127  }