From: Martin Mares Date: Sun, 9 Nov 2014 17:04:26 +0000 (+0100) Subject: Rename X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=b3440b9f551d5a1c907dca1d2f9018e945f2ffa0;p=ursary.git Rename --- diff --git a/Makefile b/Makefile index 40e97df..fb1b4d6 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,10 @@ LIBUCW_LIBS := $(shell PKG_CONFIG_PATH=$(LIBUCW_PKG) pkg-config --libs libucw) CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) $(LIBPULSE_CFLAGS) -g2 LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) -all: ut +all: ursaryd -ut: ut.o nocturn.o pulse-ucw.o +ursaryd: ursaryd.o nocturn.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 .#*` + rm -f ursaryd diff --git a/ursaryd.c b/ursaryd.c new file mode 100644 index 0000000..a127744 --- /dev/null +++ b/ursaryd.c @@ -0,0 +1,707 @@ +#define LOCAL_DEBUG + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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; +}; + +#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; + +static void update_ring_from_sink(int ring, const char *sink_name) +{ + struct pulse_sink *s = pulse_sink_by_name(sink_name); + if (!s) + { + noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f); + noct_set_button(ring, 0); + return; + } + + if (s->mute) + { + noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f); + noct_set_button(ring, 1); + return; + } + + double vol = pa_sw_volume_to_linear(s->volume); + vol = CLAMP(vol, 0, 1); + int val = 0x7f * vol; + val = CLAMP(val, 12, 0x7f); + noct_set_ring(ring, RING_MODE_LEFT, val); + noct_set_button(ring, 0); +} + +struct client_map { + int rotary; + const char *client; + const char *host; + double range; +}; + +static struct client_map client_map[] = { + { 4, "Music Player Daemon", "albireo", 1 }, + { 5, "MPlayer", NULL, 1 }, + { 6, NULL, "ogion", 1 }, +}; + +#define NUM_CLIENTS ARRAY_SIZE(client_map) + +struct client_state { + double volume; + bool have_muted[2]; +}; + +static struct client_state client_state[NUM_CLIENTS]; + +static int find_client_by_rotary(int rotary) +{ + uns i; + for (i=0; i < NUM_CLIENTS; i++) + if (client_map[i].rotary == rotary) + return i; + return -1; +} + +static bool client_match_sink_input(struct client_map *cm, struct pulse_sink_input *s) +{ + if (s->client_idx < 0 || s->sink_idx < 0) + return 0; + + struct pulse_client *c = pulse_client_lookup(s->client_idx); + if (!c) + return 0; + + return ((!cm->client || !strcmp(cm->client, c->name)) && + (!cm->host || !strcmp(cm->host, c->host))); +} + +static void calc_clients(void) +{ + bzero(client_state, sizeof(client_state)); + + HASH_FOR_ALL(pulse_sink_input, s) + { + for (uns i=0; i < NUM_CLIENTS; i++) + { + struct client_map *cm = &client_map[i]; + struct client_state *cs = &client_state[i]; + if (client_match_sink_input(cm, s)) + { + DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary); + cs->volume = MAX(cs->volume, s->volume); + cs->have_muted[!!s->mute] = 1; + } + } + } + HASH_END_FOR; +} + +static void update_clients(void) +{ + calc_clients(); + + for (uns i=0; i < NUM_CLIENTS; i++) + { + struct client_map *cm = &client_map[i]; + struct client_state *cs = &client_state[i]; + if (!cs->have_muted[0] && !cs->have_muted[1]) + { + noct_set_ring(cm->rotary, RING_MODE_LEFT, 0); + noct_set_button(cm->rotary, 0); + } + else if (!cs->have_muted[0]) + { + noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f); + noct_set_button(cm->rotary, 1); + } + else + { + double vol = pa_sw_volume_to_linear(cs->volume); + vol = CLAMP(vol, 0, cm->range); + int val = 0x7f * vol / cm->range; + val = CLAMP(val, 12, 0x7f); + noct_set_ring(cm->rotary, RING_MODE_LEFT, val); + noct_set_button(cm->rotary, 0); + } + } +} + +static void do_update(struct main_timer *t) +{ + timer_del(t); + if (!noct_is_ready()) + { + DBG("## UPDATE: Nocturn is not ready"); + return; + } + + static bool dead; + if (pulse_state != PS_ONLINE) + { + DBG("## UPDATE: Pulse is not online"); + for (int i=0; i<=8; i++) + noct_set_ring(i, RING_MODE_LEFT, 0); + for (int i=0; i<8; i++) + { + noct_set_button(i, 1); + noct_set_button(i+8, 0); + } + dead = 1; + return; + } + if (dead) + { + DBG("## UPDATE: Waking up from the dead"); + for (int i=0; i<=8; i++) + noct_set_ring(i, RING_MODE_LEFT, 0); + for (int i=0; i<16; i++) + noct_set_button(i, 0); + dead = 0; + } + + DBG("## UPDATE"); + pulse_dump(); + + update_ring_from_sink(0, "ursarium"); + update_ring_from_sink(1, "catarium"); + update_clients(); +} + +void schedule_update(void) +{ + timer_add_rel(&update_timer, 10); +} + +static void update_sink_from_rotary(int delta, const char *sink_name) +{ + struct pulse_sink *s = pulse_sink_by_name(sink_name); + if (!s) + return; + + double vol = pa_sw_volume_to_linear(s->volume); + vol += delta * 0.02; + vol = CLAMP(vol, 0, 1); + pa_volume_t pavol = pa_sw_volume_from_linear(vol); + if (pavol == s->volume) + return; + pa_cvolume cvol; + 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); +} + +static void update_client_from_rotary(int rotary, int delta) +{ + int i = find_client_by_rotary(rotary); + if (i < 0) + return; + struct client_map *cm = &client_map[i]; + struct client_state *cs = &client_state[i]; + + calc_clients(); + double vol = pa_sw_volume_to_linear(cs->volume); + vol += delta * 0.02; + vol = CLAMP(vol, 0, cm->range); + pa_volume_t pavol = pa_sw_volume_from_linear(vol); + + HASH_FOR_ALL(pulse_sink_input, s) + { + if (client_match_sink_input(cm, s) && 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); + } + } + HASH_END_FOR; +} + +void notify_rotary(int rotary, int delta) +{ + if (pulse_state != PS_ONLINE) + { + DBG("## NOTIFY: Pulse is not online"); + return; + } + + switch (rotary) + { + case 0: + update_sink_from_rotary(delta, "ursarium"); + break; + case 1: + update_sink_from_rotary(delta, "catarium"); + break; + case 8: + update_sink_from_rotary(delta, "ursarium"); + update_sink_from_rotary(delta, "catarium"); + break; + default: + update_client_from_rotary(rotary, delta); + } +} + +static void update_sink_mute_from_button(int on, const char *sink_name) +{ + if (!on) + return; + + struct pulse_sink *s = pulse_sink_by_name(sink_name); + if (!s) + 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); +} + +static void update_client_from_button(int button, int on) +{ + if (button >= 8 || !on) + return; + + int i = find_client_by_rotary(button); + if (i < 0) + return; + struct client_map *cm = &client_map[i]; + struct client_state *cs = &client_state[i]; + + calc_clients(); + if (!cs->have_muted[0] && !cs->have_muted[1]) + return; + uns mute = !cs->have_muted[1]; + + HASH_FOR_ALL(pulse_sink_input, s) + { + if (client_match_sink_input(cm, s)) + { + 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); + } + } + HASH_END_FOR; +} + +void notify_button(int button, int on) +{ + if (pulse_state != PS_ONLINE) + { + DBG("## NOTIFY: Pulse is not online"); + return; + } + + switch (button) + { + case 0: + update_sink_mute_from_button(on, "ursarium"); + break; + case 1: + update_sink_mute_from_button(on, "catarium"); + break; + default: + update_client_from_button(button, on); + } +} + +int main(int argc UNUSED, char **argv) +{ + log_init(argv[0]); + main_init(); + update_timer.handler = do_update; + + noct_init(); + + msg(L_INFO, "Initializing PulseAudio"); + pulse_init(); + + msg(L_INFO, "Entering main loop"); + main_loop(); + + return 0; +} diff --git a/ut.c b/ut.c deleted file mode 100644 index a127744..0000000 --- a/ut.c +++ /dev/null @@ -1,707 +0,0 @@ -#define LOCAL_DEBUG - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#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; -}; - -#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; - -static void update_ring_from_sink(int ring, const char *sink_name) -{ - struct pulse_sink *s = pulse_sink_by_name(sink_name); - if (!s) - { - noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f); - noct_set_button(ring, 0); - return; - } - - if (s->mute) - { - noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f); - noct_set_button(ring, 1); - return; - } - - double vol = pa_sw_volume_to_linear(s->volume); - vol = CLAMP(vol, 0, 1); - int val = 0x7f * vol; - val = CLAMP(val, 12, 0x7f); - noct_set_ring(ring, RING_MODE_LEFT, val); - noct_set_button(ring, 0); -} - -struct client_map { - int rotary; - const char *client; - const char *host; - double range; -}; - -static struct client_map client_map[] = { - { 4, "Music Player Daemon", "albireo", 1 }, - { 5, "MPlayer", NULL, 1 }, - { 6, NULL, "ogion", 1 }, -}; - -#define NUM_CLIENTS ARRAY_SIZE(client_map) - -struct client_state { - double volume; - bool have_muted[2]; -}; - -static struct client_state client_state[NUM_CLIENTS]; - -static int find_client_by_rotary(int rotary) -{ - uns i; - for (i=0; i < NUM_CLIENTS; i++) - if (client_map[i].rotary == rotary) - return i; - return -1; -} - -static bool client_match_sink_input(struct client_map *cm, struct pulse_sink_input *s) -{ - if (s->client_idx < 0 || s->sink_idx < 0) - return 0; - - struct pulse_client *c = pulse_client_lookup(s->client_idx); - if (!c) - return 0; - - return ((!cm->client || !strcmp(cm->client, c->name)) && - (!cm->host || !strcmp(cm->host, c->host))); -} - -static void calc_clients(void) -{ - bzero(client_state, sizeof(client_state)); - - HASH_FOR_ALL(pulse_sink_input, s) - { - for (uns i=0; i < NUM_CLIENTS; i++) - { - struct client_map *cm = &client_map[i]; - struct client_state *cs = &client_state[i]; - if (client_match_sink_input(cm, s)) - { - DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary); - cs->volume = MAX(cs->volume, s->volume); - cs->have_muted[!!s->mute] = 1; - } - } - } - HASH_END_FOR; -} - -static void update_clients(void) -{ - calc_clients(); - - for (uns i=0; i < NUM_CLIENTS; i++) - { - struct client_map *cm = &client_map[i]; - struct client_state *cs = &client_state[i]; - if (!cs->have_muted[0] && !cs->have_muted[1]) - { - noct_set_ring(cm->rotary, RING_MODE_LEFT, 0); - noct_set_button(cm->rotary, 0); - } - else if (!cs->have_muted[0]) - { - noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f); - noct_set_button(cm->rotary, 1); - } - else - { - double vol = pa_sw_volume_to_linear(cs->volume); - vol = CLAMP(vol, 0, cm->range); - int val = 0x7f * vol / cm->range; - val = CLAMP(val, 12, 0x7f); - noct_set_ring(cm->rotary, RING_MODE_LEFT, val); - noct_set_button(cm->rotary, 0); - } - } -} - -static void do_update(struct main_timer *t) -{ - timer_del(t); - if (!noct_is_ready()) - { - DBG("## UPDATE: Nocturn is not ready"); - return; - } - - static bool dead; - if (pulse_state != PS_ONLINE) - { - DBG("## UPDATE: Pulse is not online"); - for (int i=0; i<=8; i++) - noct_set_ring(i, RING_MODE_LEFT, 0); - for (int i=0; i<8; i++) - { - noct_set_button(i, 1); - noct_set_button(i+8, 0); - } - dead = 1; - return; - } - if (dead) - { - DBG("## UPDATE: Waking up from the dead"); - for (int i=0; i<=8; i++) - noct_set_ring(i, RING_MODE_LEFT, 0); - for (int i=0; i<16; i++) - noct_set_button(i, 0); - dead = 0; - } - - DBG("## UPDATE"); - pulse_dump(); - - update_ring_from_sink(0, "ursarium"); - update_ring_from_sink(1, "catarium"); - update_clients(); -} - -void schedule_update(void) -{ - timer_add_rel(&update_timer, 10); -} - -static void update_sink_from_rotary(int delta, const char *sink_name) -{ - struct pulse_sink *s = pulse_sink_by_name(sink_name); - if (!s) - return; - - double vol = pa_sw_volume_to_linear(s->volume); - vol += delta * 0.02; - vol = CLAMP(vol, 0, 1); - pa_volume_t pavol = pa_sw_volume_from_linear(vol); - if (pavol == s->volume) - return; - pa_cvolume cvol; - 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); -} - -static void update_client_from_rotary(int rotary, int delta) -{ - int i = find_client_by_rotary(rotary); - if (i < 0) - return; - struct client_map *cm = &client_map[i]; - struct client_state *cs = &client_state[i]; - - calc_clients(); - double vol = pa_sw_volume_to_linear(cs->volume); - vol += delta * 0.02; - vol = CLAMP(vol, 0, cm->range); - pa_volume_t pavol = pa_sw_volume_from_linear(vol); - - HASH_FOR_ALL(pulse_sink_input, s) - { - if (client_match_sink_input(cm, s) && 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); - } - } - HASH_END_FOR; -} - -void notify_rotary(int rotary, int delta) -{ - if (pulse_state != PS_ONLINE) - { - DBG("## NOTIFY: Pulse is not online"); - return; - } - - switch (rotary) - { - case 0: - update_sink_from_rotary(delta, "ursarium"); - break; - case 1: - update_sink_from_rotary(delta, "catarium"); - break; - case 8: - update_sink_from_rotary(delta, "ursarium"); - update_sink_from_rotary(delta, "catarium"); - break; - default: - update_client_from_rotary(rotary, delta); - } -} - -static void update_sink_mute_from_button(int on, const char *sink_name) -{ - if (!on) - return; - - struct pulse_sink *s = pulse_sink_by_name(sink_name); - if (!s) - 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); -} - -static void update_client_from_button(int button, int on) -{ - if (button >= 8 || !on) - return; - - int i = find_client_by_rotary(button); - if (i < 0) - return; - struct client_map *cm = &client_map[i]; - struct client_state *cs = &client_state[i]; - - calc_clients(); - if (!cs->have_muted[0] && !cs->have_muted[1]) - return; - uns mute = !cs->have_muted[1]; - - HASH_FOR_ALL(pulse_sink_input, s) - { - if (client_match_sink_input(cm, s)) - { - 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); - } - } - HASH_END_FOR; -} - -void notify_button(int button, int on) -{ - if (pulse_state != PS_ONLINE) - { - DBG("## NOTIFY: Pulse is not online"); - return; - } - - switch (button) - { - case 0: - update_sink_mute_from_button(on, "ursarium"); - break; - case 1: - update_sink_mute_from_button(on, "catarium"); - break; - default: - update_client_from_button(button, on); - } -} - -int main(int argc UNUSED, char **argv) -{ - log_init(argv[0]); - main_init(); - update_timer.handler = do_update; - - noct_init(); - - msg(L_INFO, "Initializing PulseAudio"); - pulse_init(); - - msg(L_INFO, "Entering main loop"); - main_loop(); - - return 0; -}