1#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4#include <string.h>
5#include <getopt.h>
6
7#include <pulse/pulseaudio.h>
8
9static pa_mainloop *mainloop = NULL;
10static pa_context *context = NULL;
11static int list_mode = 0;
12static int toggle_mode = 0;
13
14static pa_sink_info **sinks = NULL;
15static unsigned int sink_count = 0;
16static unsigned int current_sink_index = 0;
17
18void server_info_callback(pa_context *c, const pa_server_info *i, void *userdata);
19void set_default_sink_callback(pa_context *c, int success, void *userdata);
20
21int get_output_type(const char *input, char *output, size_t output_size) {
22 if (!input || !output || output_size == 0) return -1;
23
24 const char *last_dot = strrchr(input, '.');
25 if (!last_dot) return -1;
26 last_dot++;
27
28 const char *hyphen = strchr(last_dot, '-');
29 size_t len = hyphen ? (size_t)(hyphen - last_dot) : strlen(last_dot);
30
31 if (len >= output_size) len = output_size - 1;
32
33 strncpy(output, last_dot, len);
34 output[len] = '\0';
35
36 return 0;
37}
38
39void sink_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
40 (void)userdata;
41
42 if (eol > 0) {
43 if (list_mode) {
44 pa_mainloop_quit(mainloop, 0);
45 } else if (toggle_mode) {
46 pa_operation_unref(pa_context_get_server_info(c, server_info_callback, NULL));
47 }
48 return;
49 }
50
51 if (list_mode) {
52 int is_active = 0;
53 if (i->state == PA_SINK_RUNNING) {
54 is_active = 1;
55 }
56
57 char output_type[64];
58 if (get_output_type(i->name, output_type, sizeof(output_type)) == 0) {
59 printf(" %s%s", output_type, is_active ? "*" : "");
60 }
61
62 } else if (toggle_mode) {
63 sinks = realloc(sinks, (sink_count + 1) * sizeof(pa_sink_info*));
64 sinks[sink_count] = malloc(sizeof(pa_sink_info));
65
66 memcpy(sinks[sink_count], i, sizeof(pa_sink_info));
67
68 if (i->name) {
69 size_t name_len = strlen(i->name) + 1;
70 char *name_copy = malloc(name_len);
71 strcpy(name_copy, i->name);
72 sinks[sink_count]->name = name_copy;
73 }
74
75 if (i->description) {
76 size_t desc_len = strlen(i->description) + 1;
77 char *desc_copy = malloc(desc_len);
78 strcpy(desc_copy, i->description);
79 sinks[sink_count]->description = desc_copy;
80 }
81
82 sink_count++;
83 }
84}
85
86void set_default_sink_callback(pa_context *c, int success, void *userdata) {
87 (void)userdata;
88
89 if (success) {
90 printf("Successfully switched default sink\n");
91 } else {
92 int error = pa_context_errno(c);
93 fprintf(stderr, "Failed to switch default sink: %s (error code: %d)\n", pa_strerror(error), error);
94 }
95 pa_mainloop_quit(mainloop, success ? 0 : 1);
96}
97
98void server_info_callback(pa_context *c, const pa_server_info *i, void *userdata) {
99 (void)userdata;
100
101 if (!i) {
102 pa_mainloop_quit(mainloop, 1);
103 return;
104 }
105
106 // Find current default sink in our list.
107 for (unsigned int j = 0; j < sink_count; j++) {
108 if (strcmp(sinks[j]->name, i->default_sink_name) == 0) {
109 current_sink_index = j;
110 break;
111 }
112 }
113
114 // Switch to next sink.
115 unsigned int next_sink_index = (current_sink_index + 1) % sink_count;
116 printf("Switching from %s to %s\n",
117 sinks[current_sink_index]->description,
118 sinks[next_sink_index]->description);
119
120 // Set the new default sink with callback.
121 pa_operation_unref(pa_context_set_default_sink(c, sinks[next_sink_index]->name, set_default_sink_callback, NULL));
122}
123
124void state_callback(pa_context *c, void *userdata) {
125 (void)userdata;
126
127 switch (pa_context_get_state(c)) {
128 case PA_CONTEXT_READY:
129 if (list_mode) {
130 pa_operation_unref(pa_context_get_sink_info_list(c, sink_callback, NULL));
131 } else if (toggle_mode) {
132 pa_operation_unref(pa_context_get_sink_info_list(c, sink_callback, NULL));
133 }
134 break;
135 case PA_CONTEXT_FAILED:
136 case PA_CONTEXT_TERMINATED:
137 pa_mainloop_quit(mainloop, 1);
138 break;
139 default:
140 break;
141 }
142}
143
144void print_usage(const char *program_name) {
145 printf("Usage: %s [OPTIONS]\n", program_name);
146 printf("Options:\n");
147 printf(" -l, --list List all available audio sinks\n");
148 printf(" -t, --toggle Toggle between available audio sinks\n");
149 printf(" -h, --help Show this help message\n");
150}
151
152int main(int argc, char *argv[]) {
153 int opt;
154 const char *short_options = "lth";
155 struct option long_options[] = {
156 {"list", no_argument, 0, 'l'},
157 {"toggle", no_argument, 0, 't'},
158 {"help", no_argument, 0, 'h'},
159 {0, 0, 0, 0}
160 };
161
162 while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
163 switch (opt) {
164 case 'l':
165 list_mode = 1;
166 break;
167 case 't':
168 toggle_mode = 1;
169 break;
170 case 'h':
171 print_usage(argv[0]);
172 return 0;
173 default:
174 print_usage(argv[0]);
175 return 1;
176 }
177 }
178
179 if (!list_mode && !toggle_mode) {
180 fprintf(stderr, "Error: Please specify either -l (list) or -t (toggle) option\n");
181 print_usage(argv[0]);
182 return 1;
183 }
184
185 if (list_mode && toggle_mode) {
186 fprintf(stderr, "Error: Cannot use both -l and -t options simultaneously\n");
187 return 1;
188 }
189
190 mainloop = pa_mainloop_new();
191 context = pa_context_new(pa_mainloop_get_api(mainloop), "SinkLister");
192
193 if (pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) {
194 fprintf(stderr, "Failed to connect to PulseAudio server: %s\n", pa_strerror(pa_context_errno(context)));
195 return 1;
196 }
197
198 pa_context_set_state_callback(context, state_callback, NULL);
199
200 int retval;
201 pa_mainloop_run(mainloop, &retval);
202
203 // Cleanup for toggle mode
204 if (toggle_mode && sinks) {
205 for (unsigned int i = 0; i < sink_count; i++) {
206 if (sinks[i]->name) {
207 free((void*)sinks[i]->name);
208 }
209 if (sinks[i]->description) {
210 free((void*)sinks[i]->description);
211 }
212 free(sinks[i]);
213 }
214 free(sinks);
215 }
216
217 pa_context_disconnect(context);
218 pa_context_unref(context);
219 pa_mainloop_free(mainloop);
220
221 return retval;
222}