#include #include #include #define APPLICATION_NAME "pavolctld" #define ROUND_POS(x) ((int)((x) + 0.5f)) #define VOLUME_MAX (PA_VOLUME_NORM + (PA_VOLUME_NORM / 2)) // 150% max //TODO pa_operation_unref(o); /* context vars */ static int default_sink_index; // should always point to default sink static int command_sink_index = -1; // commands will modify this sink /* output callbacks */ static void sink_info_volume_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { pa_volume_t vol; if (eol) return; vol = pa_cvolume_avg(&i->volume); printf("v%d,%d,%.02f,%d\n", i->index, ROUND_POS(vol*100.0f/PA_VOLUME_NORM), pa_sw_volume_to_dB(vol), i->mute); fflush(stdout); } static void sink_info_sink_desc_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { if (eol) return; printf("s%d,%s,%s\n", i->index, i->name, i->description); fflush(stdout); } static void sink_info_default_index_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { if (eol) return; default_sink_index = i->index; // set command sink equal to the default sink on startup if (command_sink_index == -1) command_sink_index = default_sink_index; printf("f%d\n", default_sink_index); fflush(stdout); } static void server_info_default_sink_callback(pa_context *c, const pa_server_info *i, void *userdata) { pa_context_get_sink_info_by_name(c, i->default_sink_name, sink_info_default_index_callback, userdata); } /* subscription callback is called whenever a listened-to event occurs */ static void subscription_callback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_SINK: // check if sink was removed if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { printf("x%d\n", idx); fflush(stdout); return; } // check if sink was added if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) pa_context_get_sink_info_by_index(c, idx, sink_info_sink_desc_callback, userdata); // output the volume pa_context_get_sink_info_by_index(c, idx, sink_info_volume_callback, userdata); break; case PA_SUBSCRIPTION_EVENT_SERVER: pa_context_get_server_info(c, server_info_default_sink_callback, userdata); break; } } /* context state will really only change at startup or exit etc. */ static void context_state_callback(pa_context *c, void *userdata) { if (pa_context_get_state(c) == PA_CONTEXT_READY) { pa_context_set_subscribe_callback(c, subscription_callback, userdata); // output initial default sink pa_context_get_server_info(c, server_info_default_sink_callback, userdata); // output initial sink descriptions for each sink pa_context_get_sink_info_list(c, sink_info_sink_desc_callback, userdata); // output initial volume levels for each sink pa_context_get_sink_info_list(c, sink_info_volume_callback, userdata); // subscribe to sink & server events to listen to changes // other events can be subscribed to if desired pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL); } } /* stdin commands */ static void sink_info_change_command_sink_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { int useridx = *(int *)userdata; if (eol) { // callbacks are called twice if successful, with eol set the 2nd time // so this is the easiest way to check if cmd was successful if (useridx != command_sink_index) { printf("e2,sink %d does not exist\n", useridx); fflush(stdout); } return; } command_sink_index = i->index; } static void change_command_sink(pa_context *c, char *cmd) { int useridx; if (cmd[1] == '\n') { command_sink_index = default_sink_index; return; } useridx = atoi(&cmd[1]); if (useridx == command_sink_index) return; pa_context_get_sink_info_by_index(c, useridx, sink_info_change_command_sink_callback, (void *)&useridx); } static void sink_info_change_default_sink_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { int useridx = *(int *)userdata; if (eol) { if (useridx != default_sink_index) { printf("e3,sink %d does not exist\n", useridx); // same as sink_info_change_command_sink_callback fflush(stdout); } return; } pa_context_set_default_sink(c, i->name, NULL, NULL); } static void change_default_sink(pa_context *c, char *cmd) { int useridx = cmd[1] == '\n' ? command_sink_index : atoi(&cmd[1]); if (useridx == default_sink_index) return; pa_context_get_sink_info_by_index(c, useridx, sink_info_change_default_sink_callback, (void *)&useridx); } static void sink_info_set_volume_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { char *cmd = (char *)userdata; pa_cvolume *cvol; pa_volume_t vol; if (eol) return; if (cmd[1] == '\n') { pa_context_get_sink_info_by_index(c, i->index, sink_info_volume_callback, userdata); return; } cvol = (pa_cvolume *)&i->volume; // disregard const qualifier, avoid warnings :) vol = (pa_volume_t)(atof(&cmd[1]) / 100 * PA_VOLUME_NORM); // sneaky substring, super safe switch (cmd[1]) { case '+': cvol = pa_cvolume_inc(cvol, vol); break; case '-': cvol = pa_cvolume_dec(cvol, vol * -1); break; // should be no underflow w/ uint32 since 0dB is only 2^16 default : cvol = pa_cvolume_set(cvol, i->volume.channels, vol); break; } if (pa_cvolume_avg(cvol) > VOLUME_MAX) cvol = pa_cvolume_set(cvol, i->volume.channels, VOLUME_MAX); pa_context_set_sink_volume_by_index(c, i->index, cvol, NULL, NULL); } static void sink_info_set_mute_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { char *cmd = (char *)userdata; if (eol) return; if (cmd[1] == '\n') { pa_context_set_sink_mute_by_index(c, i->index, !i->mute, NULL, NULL); return; } pa_context_set_sink_mute_by_index(c, i->index, atoi(&cmd[1]), NULL, NULL); } /* handle stdin commands */ static char *no_newline(char *s) { uint8_t i; // stdin buffer is 256 char long at most so this is sufficient for(i = 0; s[i] != '\n' && s[i] != '\0'; i++); s[i] = '\0'; return s; } static void handle_command(pa_context *c, char *cmd) { if (command_sink_index == -1) { printf("e0,command '%s' sent while "APPLICATION_NAME" still initializing\n", no_newline(cmd)); fflush(stdout); return; } // send the command to each callback using userdata switch (cmd[0]) { case 's': change_command_sink(c, cmd); break; case 'f': change_default_sink(c, cmd); break; case 'v': pa_context_get_sink_info_by_index(c, command_sink_index, sink_info_set_volume_callback, (void *)cmd); break; case 'm': pa_context_get_sink_info_by_index(c, command_sink_index, sink_info_set_mute_callback, (void *)cmd); break; default: printf("e1,unrecognized command '%s'\n", no_newline(cmd)); fflush(stdout); break; } } /* main */ int main() { // initialize threaded pulse mainloop pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new(); pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); pa_context *context = pa_context_new(mainloop_api, APPLICATION_NAME); char stdin_buffer[256]; // don't change buf len unless you change the no_newline func pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); pa_context_set_state_callback(context, context_state_callback, NULL); // main loop pa_threaded_mainloop_start(mainloop); // listen for commands while (fgets(stdin_buffer, sizeof(stdin_buffer), stdin) != NULL) handle_command(context, stdin_buffer); // clean up pa_context_disconnect(context); pa_context_unref(context); pa_threaded_mainloop_free(mainloop); return 0; }