From 1e064f8a0a3e3c5d9b8b3cf3f2faf65f4ca55b66 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke Date: Tue, 13 Dec 2011 10:59:52 +0100 Subject: config: Read keymap from configuration file This needed a split of the switch that handled input before into several small callback functions. --- src/Makefile.am | 4 +- src/command.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/command.h | 33 ++++++++ src/config.c | 76 +++++++++++++++++++ src/config.h | 22 ++++++ src/config.ini | 14 ++++ src/interface.c | 195 ++--------------------------------------------- src/interface.h | 2 + 8 files changed, 386 insertions(+), 191 deletions(-) create mode 100644 src/command.c create mode 100644 src/command.h diff --git a/src/Makefile.am b/src/Makefile.am index ed5d781..0d527f6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ bin_PROGRAMS = pa-sink-ctl -pa_sink_ctl_SOURCES = interface.c config.c pa-sink-ctl.c +pa_sink_ctl_SOURCES = interface.c command.c config.c pa-sink-ctl.c EXTRA_pa_sink_ctl_SOURCES = unix_signal.c if !HAVE_SIGNALFD @@ -11,7 +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 config.h pa-sink-ctl.h sink.h unix_signal.h +noinst_HEADERS = interface.h command.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/command.c b/src/command.c new file mode 100644 index 0000000..0491831 --- /dev/null +++ b/src/command.c @@ -0,0 +1,231 @@ +/* + * pa-sink-ctl - NCurses based Pulseaudio control client + * Copyright (C) 2011 Benjamin Franzke + * Copyright (C) 2010 Jan Klemkow + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "pa-sink-ctl.h" +#include "interface.h" +#include "sink.h" +#include "command.h" + +static int +sink_input_len(struct context *ctx, struct sink_info *sink) +{ + int len = 0; + GList *l; + + for (l = ctx->input_list; l; l = l->next) { + struct sink_input_info *input = l->data; + + if (input->sink == sink->index) + len++; + } + + return len; +} + +static struct sink_input_info * +sink_get_nth_input(struct context *ctx, struct sink_info *sink, int n) +{ + GList *l; + int i = 0; + + for (l = ctx->input_list; l; l = l->next) { + struct sink_input_info *input = l->data; + if (input->sink != sink->index) + continue; + if (i++ == n) + return input; + } + + return NULL; +} + +static void +up(struct context *ctx, int key) +{ + struct sink_info *sink = NULL; + + if (ctx->chooser_input == SELECTED_SINK && + ctx->chooser_sink > 0) { + sink = g_list_nth_data(ctx->sink_list, --ctx->chooser_sink); + /* autoassigment to SELECTED_SINK (=-1) if length = 0 */ + ctx->chooser_input = sink_input_len(ctx, sink) - 1; + } else if (ctx->chooser_input >= 0) + --ctx->chooser_input; + + print_sink_list(ctx); +} + +static void +down(struct context *ctx, int key) +{ + struct sink_info *sink; + + sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); + if (ctx->chooser_input == (sink_input_len(ctx, sink) - 1) && + ctx->chooser_sink < (gint) g_list_length(ctx->sink_list)-1) { + ++ctx->chooser_sink; + ctx->chooser_input = SELECTED_SINK; + } + else if (ctx->chooser_input < (sink_input_len(ctx, sink) - 1)) + ++ctx->chooser_input; + print_sink_list(ctx); +} + +static void +volume_change(struct context *ctx, gboolean volume_increment) +{ + struct sink_info *sink; + struct sink_input_info *input; + guint32 index; + + sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); + pa_cvolume volume; + pa_volume_t tmp_vol; + pa_operation* (*volume_set) (pa_context*, guint32, const pa_cvolume *, + pa_context_success_cb_t, gpointer); + + if (ctx->chooser_input >= 0) { + input = sink_get_nth_input(ctx, sink, ctx->chooser_input); + index = input->index; + volume = (pa_cvolume) { .channels = input->channels }; + tmp_vol = input->vol; + volume_set = pa_context_set_sink_input_volume; + } else if (ctx->chooser_input == SELECTED_SINK) { + index = sink->index; + volume = (pa_cvolume) { .channels = sink->channels }; + tmp_vol = sink->vol; + volume_set = pa_context_set_sink_volume_by_index; + } else + return; + + pa_cvolume_set(&volume, volume.channels, tmp_vol); + pa_volume_t inc = 2 * PA_VOLUME_NORM / 100; + + if (volume_increment) + if (PA_VOLUME_NORM > tmp_vol && + PA_VOLUME_NORM - tmp_vol > inc) + pa_cvolume_inc(&volume, inc); + else + pa_cvolume_set(&volume, volume.channels, + PA_VOLUME_NORM); + else + pa_cvolume_dec(&volume, inc); + + + pa_operation_unref(volume_set(ctx->context, index, &volume, + change_callback, ctx)); +} + +static void +volume_down(struct context *ctx, int key) +{ + volume_change(ctx, FALSE); +} + +static void +volume_up(struct context *ctx, int key) +{ + volume_change(ctx, TRUE); +} + +static void +mute(struct context *ctx, int key) +{ + struct sink_info *sink; + struct sink_input_info *input; + guint32 index; + gint mute; + + sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); + pa_operation* (*mute_set) (pa_context*, guint32, int, + pa_context_success_cb_t, void*); + + if (ctx->chooser_input >= 0) { + input = sink_get_nth_input(ctx, sink, ctx->chooser_input); + index = input->index; + mute = !input->mute; + mute_set = pa_context_set_sink_input_mute; + } else if (ctx->chooser_input == SELECTED_SINK) { + index = sink->index; + mute = !sink->mute; + mute_set = pa_context_set_sink_mute_by_index; + } else + return; + + pa_operation_unref(mute_set(ctx->context, index, mute, + change_callback, ctx)); +} + +static void +switch_sink(struct context *ctx, int key) +{ + struct sink_info *sink = NULL; + struct sink_input_info *input; + pa_operation *o; + gint i; + + if (ctx->chooser_input == SELECTED_SINK) + return; + sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); + input = sink_get_nth_input(ctx, sink, ctx->chooser_input); + if (g_list_length(ctx->sink_list) <= 1) + return; + if (ctx->chooser_sink < (gint) g_list_length(ctx->sink_list) - 1) + ctx->chooser_sink++; + else + ctx->chooser_sink = 0; + + sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); + /* chooser_input needs to be derived from $selected_index */ + o = pa_context_move_sink_input_by_index(ctx->context, + input->index, sink->index, + change_callback, NULL); + pa_operation_unref(o); + + /* get new chooser_input, if non, select sink as fallback */ + ctx->chooser_input = SELECTED_SINK; + i = -1; + for (GList *l = ctx->input_list; l; l = l->next) { + struct sink_input_info *t = l->data; + + if (t->index == input->index) { + ctx->chooser_input = ++i; + break; + } + if (t->sink == sink->index) + ++i; + } +} + +static void +quit_cmd(struct context *ctx, int key) +{ + quit(ctx); +} + +struct command_cb_descriptor command_cbs[] = { + { "up", up }, + { "down", down }, + { "volume-down", volume_down }, + { "volume-up", volume_up }, + { "mute", mute }, + { "switch", switch_sink }, + { "quit", quit_cmd }, + { NULL, NULL } +}; diff --git a/src/command.h b/src/command.h new file mode 100644 index 0000000..41bfd0d --- /dev/null +++ b/src/command.h @@ -0,0 +1,33 @@ +/* + * pa-sink-ctl - NCurses based Pulseaudio control client + * Copyright (C) 2011 Benjamin Franzke + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef COMMAND_H +#define COMMAND_H + +struct context; + +typedef void (*command_cb)(struct context *ctx, int key); + +struct command_cb_descriptor { + char *command; + command_cb cb; +}; + +extern struct command_cb_descriptor command_cbs[]; + +#endif /* COMMAND_H */ diff --git a/src/config.c b/src/config.c index b6b2c5d..a3535d1 100644 --- a/src/config.c +++ b/src/config.c @@ -1,9 +1,29 @@ +/* + * pa-sink-ctl - NCurses based Pulseaudio control client + * Copyright (C) 2011 Benjamin Franzke + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include "pa-sink-ctl.h" #include "config.h" +#include "command.h" + static int parse_priorities(struct config *cfg) { @@ -76,6 +96,59 @@ read_settings(struct config *cfg) return 0; } +static unsigned int +read_int(const char *string) +{ + unsigned int value; + + if (string[1] == 'x') + sscanf(string, "%x", &value); + else + sscanf(string, "%o", &value); + + return value; +} + +static int +read_input_mappings(struct config *cfg) +{ + int i, j; + gchar **list; + GError *error = NULL; + + cfg->keymap = g_hash_table_new(g_direct_hash, g_direct_equal); + if (cfg->keymap == NULL) + return -1; + + for (i = 0; command_cbs[i].command; ++i) { + char *attrib = command_cbs[i].command; + + error = NULL; + list = g_key_file_get_string_list(cfg->keyfile, "input", + attrib, NULL, &error); + if (error) { + g_printerr("error reading keymap for '%s': %s\n", + attrib, error->message); + return -1; + } + + for (j = 0; list[j]; ++j) { + int key; + if (strlen(list[j]) == 0) + continue; + + if (strlen(list[j]) > 2 && list[j][0] == '0') { + key = read_int(list[j]); + } else + key = list[j][0]; + g_hash_table_insert(cfg->keymap, GINT_TO_POINTER(key), + &command_cbs[i]); + } + } + + return 0; +} + int config_init(struct config *cfg) { @@ -109,6 +182,8 @@ config_init(struct config *cfg) if (parse_priorities(cfg) < 0) return -1; + if (read_input_mappings(cfg) < 0) + return -1; return 0; } @@ -118,6 +193,7 @@ config_uninit(struct config *cfg) { g_list_free_full(cfg->priorities, destroy_priority); + g_hash_table_destroy(cfg->keymap); g_strfreev(cfg->name_props); g_key_file_free(cfg->keyfile); } diff --git a/src/config.h b/src/config.h index 9cf320e..62fac07 100644 --- a/src/config.h +++ b/src/config.h @@ -1,11 +1,33 @@ +/* + * pa-sink-ctl - NCurses based Pulseaudio control client + * Copyright (C) 2011 Benjamin Franzke + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef CONFIG_H #define CONFIG_H +#include + struct config { GKeyFile *keyfile; GList *priorities; gchar **name_props; + + GHashTable *keymap; }; struct priority { diff --git a/src/config.ini b/src/config.ini index ecedead..cd658f1 100644 --- a/src/config.ini +++ b/src/config.ini @@ -3,6 +3,20 @@ ## List of properties to try to use as sink names, or sink name itself if empty. name-properties = device.description; device.product.name +[input] +## Key starting with a 0 and length > 1 are encoded in octal or hexadecimal, +## depending on second character being an 'x'. +## See ncurses.h for other presudo-character tokens +up = 0403:k;w +down = 0402;j;s +volume-down = 0404;h;a +volume-up = 0405;l;d +mute = m;x;M; +# switch sink input using return, tab or space +switch = 0xa;0x9; ; +# quit on q or escape +quit = q;033 + ## 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) diff --git a/src/interface.c b/src/interface.c index 1e3d41f..8d9819a 100644 --- a/src/interface.c +++ b/src/interface.c @@ -27,6 +27,7 @@ #include "interface.h" #include "sink.h" +#include "command.h" #include "pa-sink-ctl.h" #ifdef HAVE_SIGNALFD @@ -36,43 +37,6 @@ #include "unix_signal.h" #endif -#define H_MSG_BOX 3 - -#define SELECTED_SINK -1 - -static int -sink_input_len(struct context *ctx, struct sink_info *sink) -{ - int len = 0; - GList *l; - - for (l = ctx->input_list; l; l = l->next) { - struct sink_input_info *input = l->data; - - if (input->sink == sink->index) - len++; - } - - return len; -} - -static struct sink_input_info * -sink_get_nth_input(struct context *ctx, struct sink_info *sink, int n) -{ - GList *l; - int i = 0; - - for (l = ctx->input_list; l; l = l->next) { - struct sink_input_info *input = l->data; - if (input->sink != sink->index) - continue; - if (i++ == n) - return input; - } - - return NULL; -} - static gboolean interface_resize(gpointer data) { @@ -218,164 +182,17 @@ static gboolean interface_get_input(GIOChannel *source, GIOCondition condition, gpointer data) { struct context *ctx = data; + struct command_cb_descriptor *cmd; gint c; - gboolean volume_increment = TRUE; - struct sink_info *sink = NULL; - guint32 index; - pa_operation *o; if (!ctx->context_ready) return TRUE; c = wgetch(ctx->menu_win); - switch (c) { - case 'k': - case 'w': - case KEY_UP: - if (ctx->chooser_input == SELECTED_SINK && - ctx->chooser_sink > 0) { - sink = g_list_nth_data(ctx->sink_list, - --ctx->chooser_sink); - /* autoassigment to SELECTED_SINK (=-1) if length = 0 */ - ctx->chooser_input = sink_input_len(ctx, sink) - 1; - } - - else if (ctx->chooser_input >= 0) - --ctx->chooser_input; - print_sink_list(ctx); - break; - - case 'j': - case 's': - case KEY_DOWN: - sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); - if (ctx->chooser_input == (sink_input_len(ctx, sink) - 1) && - ctx->chooser_sink < (gint)g_list_length(ctx->sink_list)-1) { - ++ctx->chooser_sink; - ctx->chooser_input = SELECTED_SINK; - } - else if (ctx->chooser_input < (sink_input_len(ctx, sink) - 1)) - ++ctx->chooser_input; - print_sink_list(ctx); - break; - - case 'h': - case 'a': - case KEY_LEFT: - volume_increment = FALSE; - /* fall through */ - case 'l': - case 'd': - case KEY_RIGHT: - sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); - pa_cvolume volume; - pa_volume_t tmp_vol; - pa_operation* (*volume_set) (pa_context*, guint32, - const pa_cvolume *, - pa_context_success_cb_t, gpointer); - - if (ctx->chooser_input >= 0) { - struct sink_input_info *input = - sink_get_nth_input(ctx, sink, - ctx->chooser_input); - index = input->index; - volume = (pa_cvolume) {.channels = input->channels}; - tmp_vol = input->vol; - volume_set = pa_context_set_sink_input_volume; - } else if (ctx->chooser_input == SELECTED_SINK) { - index = sink->index; - volume = (pa_cvolume) {.channels = sink->channels}; - tmp_vol = sink->vol; - volume_set = pa_context_set_sink_volume_by_index; - } else - break; - - pa_cvolume_set(&volume, volume.channels, tmp_vol); - pa_volume_t inc = 2 * PA_VOLUME_NORM / 100; - - if (volume_increment) - if (PA_VOLUME_NORM > tmp_vol && - PA_VOLUME_NORM - tmp_vol > inc) - pa_cvolume_inc(&volume, inc); - else - pa_cvolume_set(&volume, volume.channels, - PA_VOLUME_NORM); - else - pa_cvolume_dec(&volume, inc); - - - pa_operation_unref(volume_set(ctx->context, index, &volume, - change_callback, ctx)); - break; - case 'm': - case 'x': - case 'M': - sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); - gint mute; - pa_operation* (*mute_set) (pa_context*, guint32, int, - pa_context_success_cb_t, void*); - - if (ctx->chooser_input >= 0) { - struct sink_input_info *input = - sink_get_nth_input(ctx, sink, - ctx->chooser_input); - index = input->index; - mute = !input->mute; - mute_set = pa_context_set_sink_input_mute; - } else if (ctx->chooser_input == SELECTED_SINK) { - index = sink->index; - mute = !sink->mute; - mute_set = pa_context_set_sink_mute_by_index; - } else - break; - - pa_operation_unref(mute_set(ctx->context, index, mute, - change_callback, ctx)); - break; - - case '\n': - case '\t': - case ' ': - if (ctx->chooser_input == SELECTED_SINK) - break; - sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); - struct sink_input_info *input = - sink_get_nth_input(ctx, sink, ctx->chooser_input); - if (g_list_length(ctx->sink_list) <= 1) - break; - if (ctx->chooser_sink < (gint)g_list_length(ctx->sink_list) - 1) - ctx->chooser_sink++; - else - ctx->chooser_sink = 0; - - sink = g_list_nth_data(ctx->sink_list, ctx->chooser_sink); - /* chooser_input needs to be derived from $selected_index */ - o = pa_context_move_sink_input_by_index(ctx->context, - input->index, - sink->index, - change_callback, NULL); - pa_operation_unref(o); - - /* get new chooser_input, if non, select sink as fallback */ - ctx->chooser_input = SELECTED_SINK; - gint i = -1; - for (GList *l = ctx->input_list; l; l = l->next) { - struct sink_input_info *t = l->data; - - if (t->index == input->index) { - ctx->chooser_input = ++i; - break; - } - if (t->sink == sink->index) - ++i; - } - break; - - case 'q': - default: - quit(ctx); - break; - } + + cmd = g_hash_table_lookup(ctx->config.keymap, GINT_TO_POINTER(c)); + if (cmd) + cmd->cb(ctx, c); return TRUE; } diff --git a/src/interface.h b/src/interface.h index 1e884d8..da99612 100644 --- a/src/interface.h +++ b/src/interface.h @@ -23,6 +23,8 @@ #include #include +#define SELECTED_SINK -1 +#define H_MSG_BOX 3 struct context; -- cgit