From dacdcf2516ef4520b0bb32fa34837381f65faf30 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke Date: Mon, 12 Dec 2011 21:11:19 +0100 Subject: Implement a priority based sink order assignment This is handsome when using udev based device discovery. --- src/Makefile.am | 7 +++-- src/config.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/config.h | 21 +++++++++++++ src/config.ini | 16 ++++++++++ src/pa-sink-ctl.c | 50 +++++++++++++++++++++++++----- src/pa-sink-ctl.h | 10 ++++++ src/sink.h | 1 + 7 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 src/config.c create mode 100644 src/config.h create mode 100644 src/config.ini (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 3fc41ad..ed5d781 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ bin_PROGRAMS = pa-sink-ctl -pa_sink_ctl_SOURCES = interface.c pa-sink-ctl.c +pa_sink_ctl_SOURCES = interface.c config.c pa-sink-ctl.c EXTRA_pa_sink_ctl_SOURCES = unix_signal.c if !HAVE_SIGNALFD @@ -11,4 +11,7 @@ AM_CPPFLAGS = $(PULSE_CFLAGS) $(PULSE_MAINLOOP_CFLAGS) $(GLIB_CFLAGS) \ -include $(top_builddir)/config.h pa_sink_ctl_LDADD = $(GLIB_LIBS) $(PULSE_LIBS) $(PULSE_MAINLOOP_LIBS) $(CURSES_LIBS) -noinst_HEADERS = interface.h pa-sink-ctl.h sink.h unix_signal.h +noinst_HEADERS = interface.h config.h pa-sink-ctl.h sink.h unix_signal.h + +pa_sink_ctl_xdgdir = $(sysconfdir)/xdg/pa-sink-ctl +dist_pa_sink_ctl_xdg_DATA = config.ini diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..e24ece0 --- /dev/null +++ b/src/config.c @@ -0,0 +1,93 @@ +#include +#include + +#include "pa-sink-ctl.h" +#include "config.h" + +static int +parse_priorities(struct config *cfg) +{ + gchar **groups; + struct priority p; + int i; + gsize length; + GError *error = NULL; + + groups = g_key_file_get_groups(cfg->keyfile, &length); + + for (i = 0; i < length; ++i) { + if (strncmp(groups[i], "priority", 8) != 0) + continue; + + memset(&p, 0, sizeof p); + + p.match = g_key_file_get_value(cfg->keyfile, groups[i], + "match", &error); + if (error) + goto error; + p.value = g_key_file_get_value(cfg->keyfile, groups[i], + "value", &error); + if (error) + goto error; + p.priority = g_key_file_get_integer(cfg->keyfile, groups[i], + "priority", &error); + if (error) + goto error; + + list_append_struct(cfg->priorities, p); + } + + return 0; + +error: + if (p.value) + g_free(p.value); + if (p.match) + g_free(p.match); + + g_printerr("Failed to read property in prioritiy group '%s': %s\n", + groups[i], error->message); + + return -1; +} + +static void +destroy_priority(gpointer data) +{ + struct priority *p = data; + + g_free(p->value); + g_free(p->match); + g_free(p); +} + +int +config_init(struct config *cfg) +{ + GError *error = NULL; + + memset(cfg, 0, sizeof *cfg); + cfg->keyfile = g_key_file_new(); + cfg->priorities = NULL; + + if (!g_key_file_load_from_data_dirs(cfg->keyfile, + "pa-sink-ctl/config.ini", + NULL, G_KEY_FILE_NONE, &error) + && error) { + g_printerr("Failed to open config file: %s\n", error->message); + return -1; + } + + if (parse_priorities(cfg) < 0) + return -1; + + return 0; +} + +void +config_uninit(struct config *cfg) +{ + g_list_free_full(cfg->priorities, destroy_priority); + + g_key_file_free(cfg->keyfile); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..0bdbef2 --- /dev/null +++ b/src/config.h @@ -0,0 +1,21 @@ +#ifndef CONFIG_H +#define CONFIG_H + +struct config { + GKeyFile *keyfile; + + GList *priorities; +}; + +struct priority { + gchar *match, *value; + gint priority; +}; + +int +config_init(struct config *cfg); + +void +config_uninit(struct config *cfg); + +#endif /* CONFIG_H */ diff --git a/src/config.ini b/src/config.ini new file mode 100644 index 0000000..3d4bc39 --- /dev/null +++ b/src/config.ini @@ -0,0 +1,16 @@ +[pa-sink-ctl] + +## Priority groups can be used to set the display +## order for sinks. Priority groups start with "priority". +## "match": a pulseaudio property to match against (use pacmd list-sinks) +## "value": the string to compare with +## "priority": ..to assign to matched sink (default 0) +#[priority 0] +#match=device.product.name +#value=SBx00 Azalia (Intel HDA) +#priority=2 +# +#[priority 1] +#match=device.product.name +#value=USB Headset +#priority=1 diff --git a/src/pa-sink-ctl.c b/src/pa-sink-ctl.c index 799e0f3..df51b5e 100644 --- a/src/pa-sink-ctl.c +++ b/src/pa-sink-ctl.c @@ -23,14 +23,9 @@ #include "sink.h" #include "interface.h" +#include "config.h" #include "pa-sink-ctl.h" -#define list_append_struct(list, data) \ - do { \ - (list) = g_list_append((list), \ - g_memdup(&(data), sizeof(data))); \ - } while (0) - static sink_input_info * find_sink_input_by_idx(struct context *ctx, gint idx) { @@ -101,6 +96,41 @@ sink_input_info_cb(pa_context *c, const pa_sink_input_info *i, list_append_struct(ctx->input_list, sink_input); } +static int +get_sink_priority(struct context *ctx, const pa_sink_info *sink_info) +{ + GList *l; + const char *value; + + for (l = ctx->config.priorities; l; l = l->next) { + struct priority *p = l->data; + + value = pa_proplist_gets(sink_info->proplist, p->match); + + if (g_strcmp0(value, p->value) == 0) + return p->priority; + } + + return 0; +} + +static void +add_sink(struct context *ctx, sink_info *new) +{ + GList *l, *pos = NULL; + + for (l = ctx->sink_list; l; l = l->next) { + sink_info *sink = l->data; + + if (new->priority > sink->priority) { + pos = l; + break; + } + } + ctx->sink_list = g_list_insert_before(ctx->sink_list, pos, + g_memdup(new, sizeof *new)); +} + static void sink_info_cb(pa_context *c, const pa_sink_info *i, gint is_last, gpointer userdata) @@ -132,13 +162,14 @@ sink_info_cb(pa_context *c, const pa_sink_info *i, g_strdup(pa_proplist_gets(i->proplist, "device.product.name")) : NULL, + .priority = get_sink_priority(ctx, i), }; sink_info *inlist = find_sink_by_idx(ctx, i->index); if (inlist) *inlist = sink; else - list_append_struct(ctx->sink_list, sink); + add_sink(ctx, &sink); } static void @@ -275,6 +306,9 @@ main(int argc, char** argv) ctx->context_ready = FALSE; ctx->loop = g_main_loop_new(NULL, FALSE); + + if (config_init(&ctx->config) < 0) + return -1; interface_init(ctx); @@ -310,6 +344,8 @@ main(int argc, char** argv) pa_glib_mainloop_free(m); g_main_loop_unref(ctx->loop); + config_uninit(&ctx->config); + return 0; } diff --git a/src/pa-sink-ctl.h b/src/pa-sink-ctl.h index d68401a..d8cd5bb 100644 --- a/src/pa-sink-ctl.h +++ b/src/pa-sink-ctl.h @@ -24,6 +24,8 @@ #include #include +#include "config.h" + struct context { pa_context *context; pa_operation *op; @@ -49,6 +51,8 @@ struct context { GList *input_list; gchar *status; + + struct config config; }; void @@ -57,4 +61,10 @@ quit(struct context *ctx); void change_callback(pa_context* c, gint success, gpointer); +#define list_append_struct(list, data) \ + do { \ + (list) = g_list_append((list), \ + g_memdup(&(data), sizeof(data))); \ + } while (0) + #endif diff --git a/src/sink.h b/src/sink.h index 65c23b5..f26d00f 100644 --- a/src/sink.h +++ b/src/sink.h @@ -30,6 +30,7 @@ typedef struct _sink_info { gint mute; guint8 channels; pa_volume_t vol; + gint priority; } sink_info; typedef struct _sink_input_info { -- cgit