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}