-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.go
150 lines (124 loc) · 3.3 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package main
import (
"flag"
"fmt"
"log"
"strings"
"github.com/godbus/dbus"
"github.com/sqp/pulseaudio"
)
func main() {
err := switchSink()
if err != nil {
log.Fatalln(err)
}
}
func switchSink() error {
sinksFlag := flag.String("sinks", "", "sinks to switch")
lastOnlyFlag := flag.Bool("last-only", false, "switch default sink but only last stream")
flag.Parse()
rawSinks := strings.Split(*sinksFlag, ",")
var sinks []string
for _, sink := range rawSinks {
if strings.TrimSpace(sink) != "" {
sinks = append(sinks, sink)
}
}
// Load pulseaudio DBus module if needed.
isLoaded, err := pulseaudio.ModuleIsLoaded()
if err != nil {
return fmt.Errorf("failed to check if dbus module is loaded: %v", err)
}
if !isLoaded {
err = pulseaudio.LoadModule()
if err != nil {
return fmt.Errorf("failed to load dbus module: %v", err)
}
}
// Connect to the pulseaudio dbus service.
pulse, err := pulseaudio.New()
if err != nil {
return fmt.Errorf("failed to connect to pulse: %v", err)
}
defer pulse.Close()
return doSwitch(pulse, sinks, *lastOnlyFlag)
}
func doSwitch(client *pulseaudio.Client, sinks []string, lastOnly bool) error {
// find current default sink
currentSinks, err := client.Core().ListPath("Sinks")
if err != nil {
return err
}
var currentSink string
// ignore error if no fallback sink
currentSinkPath, _ := client.Core().ObjectPath("FallbackSink")
if currentSinkPath != "" {
currentSink, err = client.Device(currentSinkPath).String("Name")
if err != nil {
return fmt.Errorf("failed to query current sink name: %v", err)
}
}
sinkNameMap := map[string]dbus.ObjectPath{}
sinkPathMap := map[dbus.ObjectPath]string{}
// fill sinks automatically if user has not specified any
fillSinks := len(sinks) == 0
for _, path := range currentSinks {
sink := client.Device(path)
name, err := sink.String("Name")
if err != nil {
return fmt.Errorf("failed to query sink name: %v", err)
}
sinkNameMap[name] = path
sinkPathMap[path] = name
if fillSinks {
sinks = append(sinks, name)
}
}
// search for current sink in target sink list
targetSink := ""
found := false
for _, name := range sinks {
if _, ok := sinkNameMap[name]; !ok {
log.Printf("can't find sink name '%v'", name)
}
if currentSink == name {
found = true
continue
}
// next after target
if found {
targetSink = name
break
}
}
if targetSink == "" {
targetSink = sinks[0]
}
targetPath, ok := sinkNameMap[targetSink]
if !ok {
return fmt.Errorf("unknown sink named '%s'", targetSink)
}
// set default sink
err = client.Core().Set("FallbackSink", targetPath)
if err != nil {
return fmt.Errorf("failed to set fallback sink: %v", err)
}
// switch active streams to target sink
streams, err := client.Core().ListPath("PlaybackStreams")
if err != nil {
return fmt.Errorf("can't list current streams: %v", err)
}
if lastOnly {
streams = streams[len(streams)-1:]
}
for _, stream := range streams {
// Get the device to query properties for the stream referenced by his path.
dev := client.Stream(stream)
err = dev.Call("org.PulseAudio.Core1.Stream.Move", 0, targetPath).Err
if err != nil {
return fmt.Errorf("failed to switch stream '%s' to target '%v': %v", stream, targetSink, err)
}
}
log.Printf("switched to sink %q", targetSink)
return nil
}