From caec944257ebac060566080403109525c02e544e Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 9 Nov 2014 20:09:57 +0100 Subject: [PATCH] Split off pulse.c --- Makefile | 2 +- pulse.c | 396 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ursaryd.c | 400 ++---------------------------------------------------- ursaryd.h | 61 ++++++++- 4 files changed, 471 insertions(+), 388 deletions(-) create mode 100644 pulse.c diff --git a/Makefile b/Makefile index fb1b4d6..f6d8d8a 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) all: ursaryd -ursaryd: ursaryd.o nocturn.o pulse-ucw.o +ursaryd: ursaryd.o nocturn.o pulse.o pulse-ucw.o clean: rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name .depend -or -name .#*` diff --git a/pulse.c b/pulse.c new file mode 100644 index 0000000..a22021f --- /dev/null +++ b/pulse.c @@ -0,0 +1,396 @@ +/* + * Asynchronous Interface to PulseAudio + * + * (c) 2014 Martin Mares + */ + +#define LOCAL_DEBUG + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "ursaryd.h" + +enum pulse_state pulse_state; +#define PULSE_STATE(s) do { pulse_state = s; DBG("Pulse: " #s); } while (0) + +clist pulse_client_list, pulse_sink_list, pulse_sink_input_list; + +static pa_context *pulse_ctx; +static struct main_timer pulse_connect_timer; + +#define SET_STRING(_field, _val) do { if (!_field || strcmp(_field, _val)) { xfree(_field); _field = xstrdup(_val); } } while (0) + +/*** Tracking of currently running asynchronous operations ***/ + +struct pulse_op { + cnode n; + pa_operation *o; + bool is_init; +}; + +static clist pulse_op_list; + +static struct pulse_op *pulse_op_new(void) +{ + struct pulse_op *op = xmalloc_zero(sizeof(*op)); + clist_add_tail(&pulse_op_list, &op->n); + return op; +} + +static void pulse_op_done(struct pulse_op *op) +{ + if (op->o) + pa_operation_unref(op->o); + clist_remove(&op->n); + xfree(op); +} + +static void pulse_op_cancel_all(void) +{ + struct pulse_op *op; + while (op = (struct pulse_op *) clist_head(&pulse_op_list)) + { + DBG("Pulse: Cancelling pending operation"); + pa_operation_cancel(op->o); + pulse_op_done(op); + } +} + +#define PULSE_ASYNC_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->o = name(__VA_ARGS__, _op); } while (0) +#define PULSE_ASYNC_INIT_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->is_init = 1; _op->o = name(__VA_ARGS__, _op); } while (0) + +static void pulse_success_cb(pa_context *ctx UNUSED, int success, void *userdata) +{ + if (!success) + msg(L_ERROR, "Pulse: Failure reported"); + pulse_op_done(userdata); +} + +/*** Debugging dumps ***/ + +void pulse_dump(void) +{ + CLIST_FOR_EACH(struct pulse_client *, c, pulse_client_list) + DBG("## Client #%d: %s host=%s", c->idx, c->name, c->host); + + CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list) + DBG("## Sink #%d: %s channels=%u volume=%u base_vol=%u mute=%u", + s->idx, s->name, s->channels, s->volume, s->base_volume, s->mute); + + CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) + DBG("## Sink input #%d: %s client=%d sink=%d channels=%u volume=%u mute=%u", + s->idx, s->name, s->client_idx, s->sink_idx, s->channels, s->volume, s->mute); +} + +static void pulse_dump_proplist(pa_proplist *pl UNUSED) +{ +#if 0 + void *iterator = NULL; + const char *key; + + while (key = pa_proplist_iterate(pl, &iterator)) + { + const char *val = pa_proplist_gets(pl, key); + DBG(" %s = %s", key, val); + } +#endif +} + +/*** Sink inputs ***/ + +#define HASH_NODE struct pulse_sink_input +#define HASH_PREFIX(x) pulse_sink_input_##x +#define HASH_KEY_ATOMIC idx +#define HASH_WANT_CLEANUP +#define HASH_WANT_LOOKUP +#define HASH_WANT_REMOVE +#define HASH_ZERO_FILL +#include + +static void pulse_sink_input_cb(pa_context *ctx UNUSED, const pa_sink_input_info *i, int eol, void *userdata) +{ + struct pulse_op *op = userdata; + + if (eol) + { + if (op->is_init) + { + PULSE_STATE(PS_ONLINE); + schedule_update(); + } + pulse_op_done(op); + return; + } + + DBG("Pulse: SINK INPUT #%u: %s client=%d sink=%d chans=%d has_vol=%d vol_rw=%d volume=%u mute=%d", + i->index, i->name, i->client, i->sink, i->channel_map.channels, i->has_volume, i->volume_writable, i->volume.values[0], i->mute); + pulse_dump_proplist(i->proplist); + + struct pulse_sink_input *s = pulse_sink_input_lookup(i->index); + if (!clist_is_linked(&s->n)) + clist_add_tail(&pulse_sink_input_list, &s->n); + SET_STRING(s->name, i->name); + s->client_idx = i->client; + s->sink_idx = i->sink; + s->channels = i->channel_map.channels; + s->volume = pa_cvolume_avg(&i->volume); + s->mute = i->mute; + schedule_update(); +} + +static void pulse_sink_input_gone(int idx) +{ + DBG("Pulse: REMOVE SINK INPUT #%d", idx); + struct pulse_sink_input *s = pulse_sink_input_lookup(idx); + clist_remove(&s->n); + pulse_sink_input_remove(s); + schedule_update(); +} + +void pulse_sink_input_set_volume(int idx, pa_cvolume *cvol) +{ + PULSE_ASYNC_RUN(pa_context_set_sink_input_volume, pulse_ctx, idx, cvol, pulse_success_cb); +} + +void pulse_sink_input_set_mute(int idx, bool mute) +{ + PULSE_ASYNC_RUN(pa_context_set_sink_input_mute, pulse_ctx, idx, mute, pulse_success_cb); +} + +/*** Sinks ***/ + +#define HASH_NODE struct pulse_sink +#define HASH_PREFIX(x) pulse_sink_##x +#define HASH_KEY_ATOMIC idx +#define HASH_WANT_CLEANUP +#define HASH_WANT_LOOKUP +#define HASH_WANT_REMOVE +#define HASH_ZERO_FILL +#include + +static void pulse_sink_cb(pa_context *ctx, const pa_sink_info *i, int eol, void *userdata) +{ + struct pulse_op *op = userdata; + + if (eol) + { + if (op->is_init) + { + PULSE_STATE(PS_GET_SINK_INPUTS); + PULSE_ASYNC_INIT_RUN(pa_context_get_sink_input_info_list, ctx, pulse_sink_input_cb); + } + pulse_op_done(op); + return; + } + + DBG("Pulse: SINK #%u: %s (%s) flags=%08x channels=%u volume=%u mute=%d base_vol=%u state=%u", + i->index, i->name, i->description, i->flags, i->channel_map.channels, i->volume.values[0], i->mute, i->base_volume, i->state); + pulse_dump_proplist(i->proplist); + + struct pulse_sink *s = pulse_sink_lookup(i->index); + if (!clist_is_linked(&s->n)) + clist_add_tail(&pulse_sink_list, &s->n); + SET_STRING(s->name, i->name); + s->channels = i->channel_map.channels; + s->volume = pa_cvolume_avg(&i->volume); + s->base_volume = i->base_volume; + s->mute = i->mute; + schedule_update(); +} + +static void pulse_sink_gone(int idx) +{ + DBG("Pulse: REMOVE SINK #%d", idx); + struct pulse_sink *s = pulse_sink_lookup(idx); + clist_remove(&s->n); + pulse_sink_remove(s); + schedule_update(); +} + +struct pulse_sink *pulse_sink_by_name(const char *name) +{ + CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list) + if (!strcmp(s->name, name)) + return s; + return NULL; +} + +void pulse_sink_set_volume(int idx, pa_cvolume *cvol) +{ + PULSE_ASYNC_RUN(pa_context_set_sink_volume_by_index, pulse_ctx, idx, cvol, pulse_success_cb); +} + +void pulse_sink_set_mute(int idx, bool mute) +{ + PULSE_ASYNC_RUN(pa_context_set_sink_mute_by_index, pulse_ctx, idx, mute, pulse_success_cb); +} + +/*** Clients ***/ + +#define HASH_NODE struct pulse_client +#define HASH_PREFIX(x) pulse_client_##x +#define HASH_KEY_ATOMIC idx +#define HASH_WANT_CLEANUP +#define HASH_WANT_LOOKUP +#define HASH_WANT_REMOVE +#define HASH_ZERO_FILL +#include + +static void pulse_client_cb(pa_context *ctx, const pa_client_info *i, int eol, void *userdata) +{ + struct pulse_op *op = userdata; + + if (eol) + { + if (op->is_init) + { + PULSE_STATE(PS_GET_SINKS); + PULSE_ASYNC_INIT_RUN(pa_context_get_sink_info_list, ctx, pulse_sink_cb); + } + pulse_op_done(op); + return; + } + + char *host = stk_strdup(pa_proplist_gets(i->proplist, "application.process.host") ? : "?"); + DBG("Pulse: CLIENT #%u: %s mod=%u drv=%s host=%s", + i->index, i->name, i->owner_module, i->driver, host); + pulse_dump_proplist(i->proplist); + + struct pulse_client *c = pulse_client_lookup(i->index); + if (!clist_is_linked(&c->n)) + clist_add_tail(&pulse_client_list, &c->n); + SET_STRING(c->name, i->name); + SET_STRING(c->host, host); + schedule_update(); +} + +static void pulse_client_gone(int idx) +{ + DBG("Pulse: REMOVE CLIENT #%d", idx); + struct pulse_client *c = pulse_client_lookup(idx); + clist_remove(&c->n); + pulse_client_remove(c); + schedule_update(); +} + +struct pulse_client *pulse_client_by_idx(int idx) +{ + return pulse_client_lookup(idx); +} + +/*** Events ***/ + +static void pulse_subscribe_done_cb(pa_context *ctx, int success, void *userdata) +{ + pulse_op_done(userdata); + + if (!success) + msg(L_ERROR, "pa_context_subscribe failed: success=%d", success); + + PULSE_STATE(PS_GET_CLIENTS); + PULSE_ASYNC_INIT_RUN(pa_context_get_client_info_list, ctx, pulse_client_cb); +} + +static void pulse_event_cb(pa_context *ctx, pa_subscription_event_type_t type, uint32_t idx, void *userdata UNUSED) +{ + DBG("Pulse: SUBSCRIBE EVENT type=%08x idx=%u", type, idx); + + uns object = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + uns action = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + switch (object) + { + case PA_SUBSCRIPTION_EVENT_CLIENT: + if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) + PULSE_ASYNC_RUN(pa_context_get_client_info, ctx, idx, pulse_client_cb); + else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) + pulse_client_gone(idx); + break; + case PA_SUBSCRIPTION_EVENT_SINK: + if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) + PULSE_ASYNC_RUN(pa_context_get_sink_info_by_index, ctx, idx, pulse_sink_cb); + else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) + pulse_sink_gone(idx); + break; + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) + PULSE_ASYNC_RUN(pa_context_get_sink_input_info, ctx, idx, pulse_sink_input_cb); + else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) + pulse_sink_input_gone(idx); + break; + } +} + +/*** Server state ***/ + +static void pulse_shutdown(void) +{ + DBG("Pulse: Shutting down"); + pulse_client_cleanup(); + pulse_sink_cleanup(); + pulse_sink_input_cleanup(); +} + +static void pulse_state_cb(pa_context *ctx, void *userdata UNUSED) +{ + int state = pa_context_get_state(ctx); + DBG("Pulse: State callback, new state = %d", state); + if (state == PA_CONTEXT_READY) + { + if (pulse_state == PS_OFFLINE) + { + PULSE_STATE(PS_SUBSCRIBE); + pa_context_set_subscribe_callback(ctx, pulse_event_cb, NULL); + PULSE_ASYNC_INIT_RUN(pa_context_subscribe, ctx, PA_SUBSCRIPTION_MASK_ALL, pulse_subscribe_done_cb); + } + } + else + { + if (pulse_state != PS_OFFLINE) + { + PULSE_STATE(PS_OFFLINE); + pulse_op_cancel_all(); + pulse_shutdown(); + schedule_update(); + } + if (state == PA_CONTEXT_FAILED && !timer_is_active(&pulse_connect_timer)) + timer_add_rel(&pulse_connect_timer, 2000); + } +} + +static void pulse_connect(struct main_timer *t) +{ + DBG("Pulse: Connecting"); + timer_del(t); + + clist_init(&pulse_op_list); + clist_init(&pulse_client_list); + clist_init(&pulse_sink_list); + clist_init(&pulse_sink_input_list); + pulse_client_init(); + pulse_sink_init(); + pulse_sink_input_init(); + + if (pulse_ctx) + pa_context_unref(pulse_ctx); + pulse_ctx = pa_context_new(&pmain_api, "ursaryd"); + + pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL); + pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); +} + +void pulse_init(void) +{ + pmain_init(); + + pulse_connect_timer.handler = pulse_connect; + timer_add_rel(&pulse_connect_timer, 0); +} diff --git a/ursaryd.c b/ursaryd.c index ec38664..7f9d00c 100644 --- a/ursaryd.c +++ b/ursaryd.c @@ -1,3 +1,9 @@ +/* + * The Ursary Audio Controls + * + * (c) 2014 Martin Mares + */ + #define LOCAL_DEBUG #include @@ -13,381 +19,6 @@ #include "ursaryd.h" -/*** Interface to PulseAudio ***/ - -static pa_context *pulse_ctx; -static struct main_timer pulse_connect_timer; - -static void pulse_dump(void); - -enum pulse_state { - PS_OFFLINE, - PS_SUBSCRIBE, - PS_GET_CLIENTS, - PS_GET_SINKS, - PS_GET_SINK_INPUTS, - PS_ONLINE, -}; - -static enum pulse_state pulse_state; -#define PULSE_STATE(s) do { pulse_state = s; DBG("Pulse: " #s); } while (0) - -// Tracking of currently running asynchronous operations -struct pulse_op { - cnode n; - pa_operation *o; - bool is_init; -}; - -static clist pulse_op_list; - -static struct pulse_op *pulse_op_new(void) -{ - struct pulse_op *op = xmalloc_zero(sizeof(*op)); - clist_add_tail(&pulse_op_list, &op->n); - return op; -} - -static void pulse_op_done(struct pulse_op *op) -{ - if (op->o) - pa_operation_unref(op->o); - clist_remove(&op->n); - xfree(op); -} - -static void pulse_op_cancel_all(void) -{ - struct pulse_op *op; - while (op = (struct pulse_op *) clist_head(&pulse_op_list)) - { - DBG("Pulse: Cancelling pending operation"); - pa_operation_cancel(op->o); - pulse_op_done(op); - } -} - -#define PULSE_ASYNC_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->o = name(__VA_ARGS__, _op); } while (0) -#define PULSE_ASYNC_INIT_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->is_init = 1; _op->o = name(__VA_ARGS__, _op); } while (0) - -static void pulse_success_cb(pa_context *ctx UNUSED, int success, void *userdata) -{ - if (!success) - msg(L_ERROR, "Pulse: Failure reported"); - pulse_op_done(userdata); -} - -static void pulse_dump_proplist(pa_proplist *pl UNUSED) -{ -#if 0 - void *iterator = NULL; - const char *key; - - while (key = pa_proplist_iterate(pl, &iterator)) - { - const char *val = pa_proplist_gets(pl, key); - DBG(" %s = %s", key, val); - } -#endif -} - -struct pulse_sink_input { - int idx; - char *name; - int client_idx; - int sink_idx; - uns channels; - uns volume; - uns mute; - int noct_client_idx; // Used by the high-level logic below -}; - -#define HASH_NODE struct pulse_sink_input -#define HASH_PREFIX(x) pulse_sink_input_##x -#define HASH_KEY_ATOMIC idx -#define HASH_WANT_CLEANUP -#define HASH_WANT_LOOKUP -#define HASH_WANT_REMOVE -#define HASH_ZERO_FILL -#include - -#define SET_STRING(_field, _val) do { if (!_field || strcmp(_field, _val)) { xfree(_field); _field = xstrdup(_val); } } while (0) - -static void pulse_sink_input_cb(pa_context *ctx UNUSED, const pa_sink_input_info *i, int eol, void *userdata) -{ - struct pulse_op *op = userdata; - - if (eol) - { - if (op->is_init) - { - PULSE_STATE(PS_ONLINE); - schedule_update(); - } - pulse_op_done(op); - return; - } - - DBG("Pulse: SINK INPUT #%u: %s client=%d sink=%d chans=%d has_vol=%d vol_rw=%d volume=%u mute=%d", - i->index, i->name, i->client, i->sink, i->channel_map.channels, i->has_volume, i->volume_writable, i->volume.values[0], i->mute); - pulse_dump_proplist(i->proplist); - - struct pulse_sink_input *s = pulse_sink_input_lookup(i->index); - SET_STRING(s->name, i->name); - s->client_idx = i->client; - s->sink_idx = i->sink; - s->channels = i->channel_map.channels; - s->volume = pa_cvolume_avg(&i->volume); - s->mute = i->mute; - schedule_update(); -} - -static void pulse_sink_input_gone(int idx) -{ - DBG("Pulse: REMOVE SINK INPUT #%d", idx); - struct pulse_sink_input *s = pulse_sink_input_lookup(idx); - pulse_sink_input_remove(s); - schedule_update(); -} - -struct pulse_sink { - int idx; - char *name; - uns channels; - uns volume; - uns base_volume; - int mute; -}; - -#define HASH_NODE struct pulse_sink -#define HASH_PREFIX(x) pulse_sink_##x -#define HASH_KEY_ATOMIC idx -#define HASH_WANT_CLEANUP -#define HASH_WANT_LOOKUP -#define HASH_WANT_REMOVE -#define HASH_ZERO_FILL -#include - -static void pulse_sink_cb(pa_context *ctx, const pa_sink_info *i, int eol, void *userdata) -{ - struct pulse_op *op = userdata; - - if (eol) - { - if (op->is_init) - { - PULSE_STATE(PS_GET_SINK_INPUTS); - PULSE_ASYNC_INIT_RUN(pa_context_get_sink_input_info_list, ctx, pulse_sink_input_cb); - } - pulse_op_done(op); - return; - } - - DBG("Pulse: SINK #%u: %s (%s) flags=%08x channels=%u volume=%u mute=%d base_vol=%u state=%u", - i->index, i->name, i->description, i->flags, i->channel_map.channels, i->volume.values[0], i->mute, i->base_volume, i->state); - pulse_dump_proplist(i->proplist); - - struct pulse_sink *s = pulse_sink_lookup(i->index); - SET_STRING(s->name, i->name); - s->channels = i->channel_map.channels; - s->volume = pa_cvolume_avg(&i->volume); - s->base_volume = i->base_volume; - s->mute = i->mute; - schedule_update(); -} - -static void pulse_sink_gone(int idx) -{ - DBG("Pulse: REMOVE SINK #%d", idx); - struct pulse_sink *s = pulse_sink_lookup(idx); - pulse_sink_remove(s); - schedule_update(); -} - -static struct pulse_sink *pulse_sink_by_name(const char *name) -{ - HASH_FOR_ALL(pulse_sink, s) - { - if (!strcmp(s->name, name)) - return s; - } - HASH_END_FOR; - return NULL; -} - -struct pulse_client { - int idx; - char *name; - char *host; -}; - -#define HASH_NODE struct pulse_client -#define HASH_PREFIX(x) pulse_client_##x -#define HASH_KEY_ATOMIC idx -#define HASH_WANT_CLEANUP -#define HASH_WANT_LOOKUP -#define HASH_WANT_REMOVE -#define HASH_ZERO_FILL -#include - -static void pulse_client_cb(pa_context *ctx, const pa_client_info *i, int eol, void *userdata) -{ - struct pulse_op *op = userdata; - - if (eol) - { - if (op->is_init) - { - PULSE_STATE(PS_GET_SINKS); - PULSE_ASYNC_INIT_RUN(pa_context_get_sink_info_list, ctx, pulse_sink_cb); - } - pulse_op_done(op); - return; - } - - char *host = stk_strdup(pa_proplist_gets(i->proplist, "application.process.host") ? : "?"); - DBG("Pulse: CLIENT #%u: %s mod=%u drv=%s host=%s", - i->index, i->name, i->owner_module, i->driver, host); - pulse_dump_proplist(i->proplist); - - struct pulse_client *c = pulse_client_lookup(i->index); - SET_STRING(c->name, i->name); - SET_STRING(c->host, host); - schedule_update(); -} - -static void pulse_client_gone(int idx) -{ - DBG("Pulse: REMOVE CLIENT #%d", idx); - struct pulse_client *c = pulse_client_lookup(idx); - pulse_client_remove(c); - schedule_update(); -} - -static void pulse_shutdown(void) -{ - DBG("Pulse: Shutting down"); - pulse_client_cleanup(); - pulse_sink_cleanup(); - pulse_sink_input_cleanup(); -} - -static void pulse_subscribe_done_cb(pa_context *ctx, int success, void *userdata) -{ - pulse_op_done(userdata); - - if (!success) - msg(L_ERROR, "pa_context_subscribe failed: success=%d", success); - - PULSE_STATE(PS_GET_CLIENTS); - PULSE_ASYNC_INIT_RUN(pa_context_get_client_info_list, ctx, pulse_client_cb); -} - -static void pulse_event_cb(pa_context *ctx, pa_subscription_event_type_t type, uint32_t idx, void *userdata UNUSED) -{ - DBG("Pulse: SUBSCRIBE EVENT type=%08x idx=%u", type, idx); - - uns object = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; - uns action = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; - switch (object) - { - case PA_SUBSCRIPTION_EVENT_CLIENT: - if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) - PULSE_ASYNC_RUN(pa_context_get_client_info, ctx, idx, pulse_client_cb); - else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) - pulse_client_gone(idx); - break; - case PA_SUBSCRIPTION_EVENT_SINK: - if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) - PULSE_ASYNC_RUN(pa_context_get_sink_info_by_index, ctx, idx, pulse_sink_cb); - else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) - pulse_sink_gone(idx); - break; - case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) - PULSE_ASYNC_RUN(pa_context_get_sink_input_info, ctx, idx, pulse_sink_input_cb); - else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) - pulse_sink_input_gone(idx); - break; - } -} - -static void pulse_state_cb(pa_context *ctx, void *userdata UNUSED) -{ - int state = pa_context_get_state(ctx); - DBG("Pulse: State callback, new state = %d", state); - if (state == PA_CONTEXT_READY) - { - if (pulse_state == PS_OFFLINE) - { - PULSE_STATE(PS_SUBSCRIBE); - pa_context_set_subscribe_callback(ctx, pulse_event_cb, NULL); - PULSE_ASYNC_INIT_RUN(pa_context_subscribe, ctx, PA_SUBSCRIPTION_MASK_ALL, pulse_subscribe_done_cb); - } - } - else - { - if (pulse_state != PS_OFFLINE) - { - PULSE_STATE(PS_OFFLINE); - pulse_op_cancel_all(); - pulse_shutdown(); - schedule_update(); - } - if (state == PA_CONTEXT_FAILED && !timer_is_active(&pulse_connect_timer)) - timer_add_rel(&pulse_connect_timer, 2000); - } -} - -static void pulse_dump(void) -{ - HASH_FOR_ALL(pulse_client, c) - { - DBG("## Client #%d: %s host=%s", c->idx, c->name, c->host); - } - HASH_END_FOR; - - HASH_FOR_ALL(pulse_sink, s) - { - DBG("## Sink #%d: %s channels=%u volume=%u base_vol=%u mute=%u", - s->idx, s->name, s->channels, s->volume, s->base_volume, s->mute); - } - HASH_END_FOR; - - HASH_FOR_ALL(pulse_sink_input, s) - { - DBG("## Sink input #%d: %s client=%d sink=%d channels=%u volume=%u mute=%u", - s->idx, s->name, s->client_idx, s->sink_idx, s->channels, s->volume, s->mute); - } - HASH_END_FOR; -} - -static void pulse_connect(struct main_timer *t) -{ - DBG("Pulse: Connecting"); - timer_del(t); - - clist_init(&pulse_op_list); - pulse_client_init(); - pulse_sink_init(); - pulse_sink_input_init(); - - if (pulse_ctx) - pa_context_unref(pulse_ctx); - pulse_ctx = pa_context_new(&pmain_api, "ursaryd"); - - pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL); - pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); -} - -static void pulse_init(void) -{ - pmain_init(); - - pulse_connect_timer.handler = pulse_connect; - timer_add_rel(&pulse_connect_timer, 0); -} - /*** High-level logic ***/ static struct main_timer update_timer; @@ -438,7 +69,7 @@ static void update_sink_from_rotary(int delta, const char *sink_name) pa_cvolume_set(&cvol, s->channels, pavol); DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]); - PULSE_ASYNC_RUN(pa_context_set_sink_volume_by_index, pulse_ctx, s->idx, &cvol, pulse_success_cb); + pulse_sink_set_volume(s->idx, &cvol); } struct client_map { @@ -477,14 +108,14 @@ static void calc_clients(void) { bzero(client_state, sizeof(client_state)); - HASH_FOR_ALL(pulse_sink_input, s) + CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) { s->noct_client_idx = -1; if (s->client_idx < 0 || s->sink_idx < 0) continue; - struct pulse_client *c = pulse_client_lookup(s->client_idx); + struct pulse_client *c = pulse_client_by_idx(s->client_idx); if (!c) continue; @@ -503,7 +134,6 @@ static void calc_clients(void) } } } - HASH_END_FOR; } static void update_clients(void) @@ -547,17 +177,16 @@ static void update_client_from_rotary(int rotary, int delta) double vol = volume_from_pa(cs->volume) + delta*0.02; pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range)); - HASH_FOR_ALL(pulse_sink_input, s) + CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) { if (s->noct_client_idx == i && s->volume != pavol) { DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol); pa_cvolume cvol; pa_cvolume_set(&cvol, s->channels, pavol); - PULSE_ASYNC_RUN(pa_context_set_sink_input_volume, pulse_ctx, s->idx, &cvol, pulse_success_cb); + pulse_sink_input_set_volume(s->idx, &cvol); } } - HASH_END_FOR; } static void do_update(struct main_timer *t) @@ -641,7 +270,7 @@ static void update_sink_mute_from_button(int on, const char *sink_name) return; DBG("## Setting mute of sink %s to %d", s->name, !s->mute); - PULSE_ASYNC_RUN(pa_context_set_sink_mute_by_index, pulse_ctx, s->idx, !s->mute, pulse_success_cb); + pulse_sink_set_mute(s->idx, !s->mute); } static void update_client_from_button(int button, int on) @@ -659,15 +288,14 @@ static void update_client_from_button(int button, int on) return; uns mute = !cs->have_muted[1]; - HASH_FOR_ALL(pulse_sink_input, s) + CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) { if (s->noct_client_idx == i) { DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute); - PULSE_ASYNC_RUN(pa_context_set_sink_input_mute, pulse_ctx, s->idx, mute, pulse_success_cb); + pulse_sink_input_set_mute(s->idx, mute); } } - HASH_END_FOR; } void notify_button(int button, int on) diff --git a/ursaryd.h b/ursaryd.h index 8342e83..46ec097 100644 --- a/ursaryd.h +++ b/ursaryd.h @@ -1,4 +1,10 @@ -/* main */ +/* + * The Ursary Audio Controls + * + * (c) 2014 Martin Mares + */ + +/* ursary.c */ void schedule_update(void); @@ -21,6 +27,59 @@ enum ring_mode { RING_MODE_SINGLE_OFF, }; +/* pulse.c */ + +enum pulse_state { + PS_OFFLINE, + PS_SUBSCRIBE, + PS_GET_CLIENTS, + PS_GET_SINKS, + PS_GET_SINK_INPUTS, + PS_ONLINE, +}; + +extern enum pulse_state pulse_state; + +struct pulse_client { + cnode n; + int idx; + char *name; + char *host; +}; + +struct pulse_sink { + cnode n; + int idx; + char *name; + uns channels; + uns volume; + uns base_volume; + int mute; +}; + +struct pulse_sink_input { + cnode n; + int idx; + char *name; + int client_idx; + int sink_idx; + uns channels; + uns volume; + uns mute; + int noct_client_idx; // Used by the high-level logic below +}; + +extern clist pulse_client_list, pulse_sink_list, pulse_sink_input_list; + +void pulse_init(void); +void pulse_dump(void); +struct pulse_sink *pulse_sink_by_name(const char *name); +void pulse_sink_set_volume(int idx, pa_cvolume *cvol); +void pulse_sink_set_mute(int idx, bool mute); +void pulse_sink_input_set_volume(int idx, pa_cvolume *cvol); +void pulse_sink_input_set_mute(int idx, bool mute); +struct pulse_client *pulse_client_by_idx(int idx); + /* pulse-ucw.c */ extern struct pa_mainloop_api pmain_api; -- 2.39.2