summaryrefslogtreecommitdiffstats
path: root/polyp/ctl_polyp.c
diff options
context:
space:
mode:
Diffstat (limited to 'polyp/ctl_polyp.c')
-rw-r--r--polyp/ctl_polyp.c455
1 files changed, 455 insertions, 0 deletions
diff --git a/polyp/ctl_polyp.c b/polyp/ctl_polyp.c
new file mode 100644
index 0000000..16043bf
--- /dev/null
+++ b/polyp/ctl_polyp.c
@@ -0,0 +1,455 @@
+/*
+ * ALSA <-> Polypaudio mixer control plugin
+ *
+ * Copyright (c) 2006 by Pierre Ossman <ossman@cendio.se>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <sys/poll.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/control_external.h>
+
+#include "polyp.h"
+
+typedef struct snd_ctl_polyp {
+ snd_ctl_ext_t ext;
+
+ snd_polyp_t *p;
+
+ char *device;
+
+ pa_cvolume volume;
+
+ int subscribed;
+ int updated;
+} snd_ctl_polyp_t;
+
+#define MIXER_NAME "Master"
+
+static void sink_info_cb(pa_context *c, const pa_sink_info *i, int is_last, void *userdata)
+{
+ snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata;
+ int chan;
+
+ if (is_last)
+ return;
+
+ assert(ctl && i);
+
+ if (ctl->volume.channels == i->volume.channels) {
+ for (chan = 0;chan < ctl->volume.channels;chan++)
+ if (i->volume.values[chan] != ctl->volume.values[chan])
+ break;
+
+ if (chan == ctl->volume.channels)
+ return;
+
+ ctl->updated = 1;
+ }
+
+ memcpy(&ctl->volume, &i->volume, sizeof(pa_cvolume));
+}
+
+static void event_cb(pa_context *c, pa_subscription_event_type_t t,
+ uint32_t index, void *userdata)
+{
+ snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata;
+ pa_operation *o;
+
+ assert(ctl && ctl->p && ctl->p->context);
+
+ o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->device,
+ sink_info_cb, ctl);
+ pa_operation_unref(o);
+}
+
+static int polyp_update_volume(snd_ctl_polyp_t *ctl)
+{
+ int err;
+ pa_operation *o;
+
+ assert(ctl && ctl->p && ctl->p->context);
+
+ o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->device,
+ sink_info_cb, ctl);
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int polyp_elem_count(snd_ctl_ext_t *ext)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ if (ctl->device)
+ return 1;
+
+ return 0;
+}
+
+static int polyp_elem_list(snd_ctl_ext_t *ext, unsigned int offset,
+ snd_ctl_elem_id_t *id)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(id, MIXER_NAME);
+
+ return 0;
+}
+
+static snd_ctl_ext_key_t polyp_find_elem(snd_ctl_ext_t *ext,
+ const snd_ctl_elem_id_t *id)
+{
+ const char *name;
+
+ name = snd_ctl_elem_id_get_name(id);
+
+ if (strcmp(name, MIXER_NAME) == 0)
+ return 0;
+
+ return SND_CTL_EXT_KEY_NOT_FOUND;
+}
+
+static int polyp_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ int *type, unsigned int *acc, unsigned int *count)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err;
+
+ assert(ctl && ctl->p);
+
+ if (key != 0)
+ return -EINVAL;
+
+ err = polyp_finish_poll(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_update_volume(ctl);
+ if (err < 0)
+ return err;
+
+ *type = SND_CTL_ELEM_TYPE_INTEGER;
+ *acc = SND_CTL_EXT_ACCESS_READWRITE;
+ *count = ctl->volume.channels;
+
+ return 0;
+}
+
+static int polyp_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *imin, long *imax, long *istep)
+{
+ if (key != 0)
+ return -EINVAL;
+
+ *istep = 1;
+ *imin = 0;
+ *imax = PA_VOLUME_NORM;
+
+ return 0;
+}
+
+static int polyp_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *value)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err, i;
+
+ assert(ctl && ctl->p);
+
+ if (key != 0)
+ return -EINVAL;
+
+ err = polyp_finish_poll(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_update_volume(ctl);
+ if (err < 0)
+ return err;
+
+ for (i = 0;i < ctl->volume.channels;i++)
+ value[i] = ctl->volume.values[i];
+
+ return 0;
+}
+
+static int polyp_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *value)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err, i;
+ pa_cvolume vol;
+ pa_operation *o;
+
+ assert(ctl && ctl->p && ctl->p->context);
+
+ if (key != 0)
+ return -EINVAL;
+
+ err = polyp_finish_poll(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_update_volume(ctl);
+ if (err < 0)
+ return err;
+
+ for (i = 0;i < ctl->volume.channels;i++)
+ if (value[i] != ctl->volume.values[i])
+ break;
+
+ if (i == ctl->volume.channels)
+ return 0;
+
+ memset(&vol, 0, sizeof(pa_cvolume));
+
+ vol.channels = ctl->volume.channels;
+ for (i = 0;i < vol.channels;i++)
+ vol.values[i] = value[i];
+
+ o = pa_context_set_sink_volume_by_name(ctl->p->context, ctl->device, &vol,
+ NULL, NULL);
+
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ return err;
+
+ return 1;
+}
+
+static void polyp_subscribe_events(snd_ctl_ext_t *ext, int subscribe)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ ctl->subscribed = !!(subscribe & SND_CTL_EVENT_MASK_VALUE);
+}
+
+static int polyp_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id,
+ unsigned int *event_mask)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ if (!ctl->updated || !ctl->subscribed)
+ return -EAGAIN;
+
+ polyp_elem_list(ext, 0, id);
+ *event_mask = SND_CTL_EVENT_MASK_VALUE;
+ ctl->updated = 0;
+
+ return 1;
+}
+
+static int polyp_ctl_poll_descriptors_count(snd_ctl_ext_t *ext)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl && ctl->p);
+
+ return polyp_poll_descriptors_count(ctl->p);
+}
+
+static int polyp_ctl_poll_descriptors(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int space)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl && ctl->p);
+
+ return polyp_poll_descriptors(ctl->p, pfd, space);
+}
+
+static int polyp_ctl_poll_revents(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int nfds, unsigned short *revents)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err;
+
+ assert(ctl && ctl->p);
+
+ err = polyp_poll_revents(ctl->p, pfd, nfds, revents);
+ if (err < 0)
+ return err;
+
+ *revents = 0;
+
+ if (ctl->updated)
+ *revents |= POLLIN;
+
+ return 0;
+}
+
+static void polyp_close(snd_ctl_ext_t *ext)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ if (ctl->p)
+ polyp_free(ctl->p);
+
+ if (ctl->device)
+ free(ctl->device);
+
+ free(ctl);
+}
+
+static snd_ctl_ext_callback_t polyp_ext_callback = {
+ .elem_count = polyp_elem_count,
+ .elem_list = polyp_elem_list,
+ .find_elem = polyp_find_elem,
+ .get_attribute = polyp_get_attribute,
+ .get_integer_info = polyp_get_integer_info,
+ .read_integer = polyp_read_integer,
+ .write_integer = polyp_write_integer,
+ .subscribe_events = polyp_subscribe_events,
+ .read_event = polyp_read_event,
+ .poll_descriptors_count = polyp_ctl_poll_descriptors_count,
+ .poll_descriptors = polyp_ctl_poll_descriptors,
+ .poll_revents = polyp_ctl_poll_revents,
+ .close = polyp_close,
+};
+
+static void server_info_cb(pa_context *c, const pa_server_info*i, void *userdata)
+{
+ snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata;
+
+ assert(ctl && i && i->default_sink_name);
+
+ ctl->device = strdup(i->default_sink_name);
+}
+
+SND_CTL_PLUGIN_DEFINE_FUNC(polyp)
+{
+ snd_config_iterator_t i, next;
+ const char *server = NULL;
+ const char *device = NULL;
+ int err;
+ snd_ctl_polyp_t *ctl;
+ pa_operation *o;
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+ continue;
+ if (strcmp(id, "server") == 0) {
+ if (snd_config_get_string(n, &server) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+ continue;
+ }
+ if (strcmp(id, "device") == 0) {
+ if (snd_config_get_string(n, &device) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ ctl = calloc(1, sizeof(*ctl));
+
+ ctl->p = polyp_new();
+ assert(ctl->p);
+
+ err = polyp_connect(ctl->p, server);
+ if (err < 0)
+ goto error;
+
+ err = polyp_start_thread(ctl->p);
+ if (err < 0)
+ goto error;
+
+ if (device)
+ ctl->device = strdup(device);
+ else {
+ o = pa_context_get_server_info(ctl->p->context, server_info_cb, ctl);
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ goto error;
+ }
+
+ pa_context_set_subscribe_callback(ctl->p->context, event_cb, ctl);
+
+ o = pa_context_subscribe(ctl->p->context, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ goto error;
+
+ ctl->ext.version = SND_CTL_EXT_VERSION;
+ ctl->ext.card_idx = 0;
+ strncpy(ctl->ext.id, "polyp", sizeof(ctl->ext.id) - 1);
+ strncpy(ctl->ext.driver, "Polypaudio plugin", sizeof(ctl->ext.driver) - 1);
+ strncpy(ctl->ext.name, "Polypaudio", sizeof(ctl->ext.name) - 1);
+ strncpy(ctl->ext.longname, "Polypaudio", sizeof(ctl->ext.longname) - 1);
+ strncpy(ctl->ext.mixername, "Polypaudio", sizeof(ctl->ext.mixername) - 1);
+ ctl->ext.poll_fd = -1;
+ ctl->ext.callback = &polyp_ext_callback;
+ ctl->ext.private_data = ctl;
+
+ err = snd_ctl_ext_create(&ctl->ext, name, mode);
+ if (err < 0)
+ goto error;
+
+ *handlep = ctl->ext.handle;
+
+ return 0;
+
+error:
+ if (ctl->device)
+ free(ctl->device);
+
+ if (ctl->p)
+ polyp_free(ctl->p);
+
+ free(ctl);
+
+ return err;
+}
+
+SND_CTL_PLUGIN_SYMBOL(polyp);