aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2018-01-06 03:54:12 +0100
committerPřemysl Janouch <p@janouch.name>2018-10-28 15:42:32 +0100
commitbb0366e8d2f92136fad3403ca758834cc8ceeb6a (patch)
treebe5fa6336f148cf614e5c18a95d40d9df9ac1e85
parentf19fca85ecb7c647566a5b0d81fa41ba568e5a74 (diff)
downloaddesktop-tools-bb0366e8d2f92136fad3403ca758834cc8ceeb6a.tar.gz
desktop-tools-bb0366e8d2f92136fad3403ca758834cc8ceeb6a.tar.xz
desktop-tools-bb0366e8d2f92136fad3403ca758834cc8ceeb6a.zip
Add paswitch: PulseAudio output switcher
Initial commit. It does what it's supposed to but it's very buggy.
-rw-r--r--CMakeLists.txt3
-rw-r--r--paswitch.c1045
-rw-r--r--poller-pa.c361
-rw-r--r--wmstatus.c344
4 files changed, 1410 insertions, 343 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 20c319c..0ca52ae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,9 @@ add_executable (wmstatus wmstatus.c)
target_link_libraries (wmstatus ${project_libraries})
add_threads (wmstatus)
+add_executable (paswitch paswitch.c)
+target_link_libraries (paswitch ${project_libraries})
+
add_executable (brightness brightness.c)
target_link_libraries (brightness ${project_libraries})
diff --git a/paswitch.c b/paswitch.c
new file mode 100644
index 0000000..afabbe0
--- /dev/null
+++ b/paswitch.c
@@ -0,0 +1,1045 @@
+/*
+ * paswitch.c: simple PulseAudio device switcher
+ *
+ * module-switch-on-connect functionality without the on-connect part.
+ *
+ * Copyright (c) 2015 - 2018, Přemysl Janouch <p@janouch.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#define LIBERTY_WANT_POLLER
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#undef PROGRAM_NAME
+#define PROGRAM_NAME "paswitch"
+#include "liberty/liberty.c"
+#include "poller-pa.c"
+
+#include <locale.h>
+#include <wchar.h>
+
+#include <langinfo.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#include <pulse/mainloop.h>
+#include <pulse/context.h>
+#include <pulse/error.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+
+// --- Utilities ---------------------------------------------------------------
+
+enum { PIPE_READ, PIPE_WRITE };
+
+static void
+cstr_set (char **s, char *new)
+{
+ free (*s);
+ *s = new;
+}
+
+static void
+log_message_custom (void *user_data, const char *quote, const char *fmt,
+ va_list ap)
+{
+ (void) user_data;
+ FILE *stream = stderr;
+
+ fprintf (stream, PROGRAM_NAME ": ");
+ fputs (quote, stream);
+ vfprintf (stream, fmt, ap);
+ fputs ("\r\n", stream);
+}
+
+// --- Application -------------------------------------------------------------
+
+struct port
+{
+ LIST_HEADER (struct port)
+
+ char *name; ///< Name of the port
+ char *description; ///< Description of the port
+ pa_port_available_t available; ///< Availability
+};
+
+static void
+port_free (struct port *self)
+{
+ free (self->name);
+ free (self->description);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+struct sink
+{
+ LIST_HEADER (struct sink)
+
+ char *name; ///< Name of the sink
+ char *description; ///< Description of the sink
+ uint32_t index; ///< Index of the sink
+ bool muted; ///< Currently muted?
+ pa_cvolume volume; ///< Current volume
+ struct port *ports; ///< All sink ports
+ size_t ports_len; ///< The number of ports
+ char *port_active; ///< Active sink port
+};
+
+static struct sink *
+sink_new (void)
+{
+ struct sink *self = xcalloc (1, sizeof *self);
+ return self;
+}
+
+static void
+sink_destroy (struct sink *self)
+{
+ free (self->name);
+ free (self->description);
+
+ for (size_t i = 0; i < self->ports_len; i++)
+ port_free (self->ports + i);
+ free (self->ports);
+
+ free (self->port_active);
+ free (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+struct sink_input
+{
+ LIST_HEADER (struct sink_input)
+
+ uint32_t index; ///< Index of the sink input
+ uint32_t sink; ///< Index of the connected sink
+};
+
+static struct sink_input *
+sink_input_new (void)
+{
+ struct sink_input *self = xcalloc (1, sizeof *self);
+ self->index = self->sink = PA_INVALID_INDEX;
+ return self;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+struct app_context
+{
+ struct poller poller; ///< Poller
+ struct poller_idle redraw_event; ///< Redraw the terminal
+ struct poller_timer make_context; ///< Start PulseAudio communication
+
+ struct poller_fd tty_event; ///< Terminal input event
+ struct poller_timer tty_timer; ///< Terminal input timeout
+ struct str tty_input_buffer; ///< Buffered terminal input
+
+ pa_mainloop_api *api; ///< PulseAudio event loop proxy
+ pa_context *context; ///< PulseAudio connection context
+
+ bool failed; ///< General PulseAudio failure
+ char *default_sink; ///< Name of the default sink
+ struct sink *sinks; ///< PulseAudio sinks
+ struct sink *sinks_tail; ///< Tail of PulseAudio sinks
+ struct sink_input *inputs; ///< PulseAudio sink inputs
+ struct sink_input *inputs_tail; ///< Tail of PulseAudio sink inputs
+
+ uint32_t selected_sink; ///< Selected sink index (PA)
+ ssize_t selected_port; ///< Selected port index (local)
+};
+
+static void
+app_context_init (struct app_context *self)
+{
+ memset (self, 0, sizeof *self);
+
+ poller_init (&self->poller);
+ self->tty_input_buffer = str_make ();
+ self->api = poller_pa_new (&self->poller);
+ self->selected_sink = PA_INVALID_INDEX;
+ self->selected_port = -1;
+}
+
+static void
+app_context_free (struct app_context *self)
+{
+ if (self->context)
+ pa_context_unref (self->context);
+
+ free (self->default_sink);
+ LIST_FOR_EACH (struct sink, iter, self->sinks)
+ sink_destroy (iter);
+ LIST_FOR_EACH (struct sink_input, iter, self->inputs)
+ free (iter);
+
+ poller_pa_destroy (self->api);
+ str_free (&self->tty_input_buffer);
+ poller_free (&self->poller);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#define VOLUME_PERCENT(x) (((x) * 100 + PA_VOLUME_NORM / 2) / PA_VOLUME_NORM)
+
+static char *
+make_volume_status (struct sink *sink)
+{
+ if (!sink->volume.channels)
+ return xstrdup ("");
+
+ struct str s = str_make ();
+ if (sink->muted)
+ str_append (&s, "Muted ");
+
+ str_append_printf (&s,
+ "%u%%", VOLUME_PERCENT (sink->volume.values[0]));
+ if (!pa_cvolume_channels_equal_to
+ (&sink->volume, sink->volume.values[0]))
+ {
+ for (size_t i = 1; i < sink->volume.channels; i++)
+ str_append_printf (&s, " / %u%%",
+ VOLUME_PERCENT (sink->volume.values[i]));
+ }
+ return str_steal (&s);
+}
+
+static char *
+make_inputs_status (struct app_context *ctx, struct sink *sink)
+{
+ int n = 0;
+ LIST_FOR_EACH (struct sink_input, input, ctx->inputs)
+ if (input->sink == sink->index)
+ n++;
+
+ if (n == 0) return NULL;
+ if (n == 1) return xstrdup_printf ("1 input");
+ return xstrdup_printf ("%d inputs", n);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#define DEFAULT_SINK "@DEFAULT_SINK@"
+
+static void
+on_sink_info (pa_context *context, const pa_sink_info *info, int eol,
+ void *userdata)
+{
+ (void) context;
+ struct app_context *ctx = userdata;
+ if (!info || eol)
+ {
+ // TODO: handle the case of when sinks disappear
+ if (ctx->selected_sink == PA_INVALID_INDEX && ctx->sinks)
+ ctx->selected_sink = ctx->sinks->index;
+
+ poller_idle_set (&ctx->redraw_event);
+ return;
+ }
+
+ struct sink *sink = sink_new ();
+ sink->name = xstrdup (info->name);
+ sink->description = xstrdup (info->description);
+ sink->index = info->index;
+ sink->muted = !!info->mute;
+ sink->volume = info->volume;
+
+ if (info->ports)
+ {
+ for (struct pa_sink_port_info **iter = info->ports; *iter; iter++)
+ sink->ports_len++;
+
+ struct port *port = sink->ports =
+ xcalloc (sizeof *sink->ports, sink->ports_len);
+ for (struct pa_sink_port_info **iter = info->ports; *iter; iter++)
+ {
+ port->name = xstrdup ((*iter)->name);
+ port->description = xstrdup ((*iter)->description);
+ port->available = (*iter)->available;
+ port++;
+ }
+ }
+ if (info->active_port)
+ sink->port_active = xstrdup (info->active_port->name);
+
+ LIST_APPEND_WITH_TAIL (ctx->sinks, ctx->sinks_tail, sink);
+}
+
+static void
+forget_sinks (struct app_context *ctx)
+{
+ LIST_FOR_EACH (struct sink, iter, ctx->sinks)
+ sink_destroy (iter);
+ ctx->sinks = ctx->sinks_tail = NULL;
+}
+
+static void
+update_sinks (struct app_context *ctx)
+{
+ // It shouldn't matter much if we interrupt this operation in the middle
+ // since we request new information right away. At least so long as
+ // replies can't overlap. Though even then we're safe, at least.
+ forget_sinks (ctx);
+
+ pa_operation_unref (pa_context_get_sink_info_list
+ (ctx->context, on_sink_info, ctx));
+}
+
+static void
+on_sink_input_info (pa_context *context, const struct pa_sink_input_info *info,
+ int eol, void *userdata)
+{
+ (void) context;
+ struct app_context *ctx = userdata;
+ if (!info || eol)
+ {
+ poller_idle_set (&ctx->redraw_event);
+ return;
+ }
+
+ struct sink_input *input = sink_input_new ();
+ input->sink = info->sink;
+ input->index = info->index;
+ LIST_APPEND_WITH_TAIL (ctx->inputs, ctx->inputs_tail, input);
+}
+
+static void
+forget_sink_inputs (struct app_context *ctx)
+{
+ LIST_FOR_EACH (struct sink_input, iter, ctx->inputs)
+ free (iter);
+ ctx->inputs = ctx->inputs_tail = NULL;
+}
+
+static void
+update_sink_inputs (struct app_context *ctx)
+{
+ // It shouldn't matter much if we interrupt this operation in the middle
+ // since we request new information right away. At least so long as
+ // replies can't overlap. Though even then we're safe, at least.
+ forget_sink_inputs (ctx);
+
+ pa_operation_unref (pa_context_get_sink_input_info_list
+ (ctx->context, on_sink_input_info, ctx));
+}
+
+static void
+on_server_info (pa_context *context, const struct pa_server_info *info,
+ void *userdata)
+{
+ (void) context;
+
+ struct app_context *ctx = userdata;
+ if (info->default_sink_name)
+ ctx->default_sink = xstrdup (info->default_sink_name);
+ else
+ cstr_set (&ctx->default_sink, NULL);
+}
+
+static void
+on_event (pa_context *context, pa_subscription_event_type_t event,
+ uint32_t index, void *userdata)
+{
+ (void) context;
+ (void) index;
+
+ struct app_context *ctx = userdata;
+ switch (event & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
+ {
+ case PA_SUBSCRIPTION_EVENT_SINK:
+ update_sinks (ctx);
+ break;
+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ update_sink_inputs (ctx);
+ break;
+ case PA_SUBSCRIPTION_EVENT_SERVER:
+ pa_operation_unref (pa_context_get_server_info (context,
+ on_server_info, userdata));
+ }
+}
+
+static void
+on_subscribe_finish (pa_context *context, int success, void *userdata)
+{
+ (void) context;
+
+ struct app_context *ctx = userdata;
+ if (!success)
+ {
+ ctx->failed = true;
+ poller_idle_set (&ctx->redraw_event);
+ }
+}
+
+static void
+on_context_state_change (pa_context *context, void *userdata)
+{
+ struct app_context *ctx = userdata;
+ switch (pa_context_get_state (context))
+ {
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ ctx->failed = true;
+ poller_idle_set (&ctx->redraw_event);
+
+ pa_context_unref (context);
+ ctx->context = NULL;
+
+ forget_sinks (ctx);
+ cstr_set (&ctx->default_sink, NULL);
+
+ // Retry after an arbitrary delay of 5 seconds
+ poller_timer_set (&ctx->make_context, 5000);
+ return;
+ case PA_CONTEXT_READY:
+ ctx->failed = false;
+ poller_idle_set (&ctx->redraw_event);
+
+ pa_context_set_subscribe_callback (context, on_event, userdata);
+ pa_operation_unref (pa_context_subscribe (context,
+ PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SINK_INPUT |
+ PA_SUBSCRIPTION_MASK_SERVER, on_subscribe_finish, userdata));
+
+ update_sinks (ctx);
+ update_sink_inputs (ctx);
+
+ pa_operation_unref (pa_context_get_server_info (context,
+ on_server_info, userdata));
+ default:
+ return;
+ }
+}
+
+static void
+on_make_context (void *user_data)
+{
+ struct app_context *ctx = user_data;
+ ctx->context = pa_context_new (ctx->api, PROGRAM_NAME);
+ pa_context_set_state_callback (ctx->context, on_context_state_change, ctx);
+ pa_context_connect (ctx->context, NULL, PA_CONTEXT_NOFLAGS, NULL);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+on_pa_finish (pa_context *context, int success, void *userdata)
+{
+ (void) context;
+ (void) success;
+ (void) userdata;
+
+ // Just like... whatever, man
+}
+
+static void
+sink_switch_port (struct app_context *ctx, struct sink *sink, size_t i)
+{
+ if (!ctx->context || !sink->port_active || !sink->ports)
+ return;
+
+ size_t current = 0;
+ for (size_t i = 0; i < sink->ports_len; i++)
+ if (!strcmp (sink->port_active, sink->ports[i].name))
+ current = i;
+
+ if (current != i)
+ {
+ pa_operation_unref (pa_context_set_sink_port_by_name (ctx->context,
+ sink->name, sink->ports[(current + 1) % sink->ports_len].name,
+ on_pa_finish, ctx));
+ }
+}
+
+static void
+sink_mute (struct app_context *ctx, struct sink *sink)
+{
+ if (!ctx->context)
+ return;
+
+ pa_operation_unref (pa_context_set_sink_mute_by_name (ctx->context,
+ sink->name, !sink->muted, on_pa_finish, ctx));
+}
+
+static void
+sink_set_volume (struct app_context *ctx, struct sink *sink, int diff)
+{
+ if (!ctx->context)
+ return;
+
+ pa_cvolume volume = sink->volume;
+ if (diff > 0)
+ pa_cvolume_inc (&volume, (pa_volume_t) diff * PA_VOLUME_NORM / 100);
+ else
+ pa_cvolume_dec (&volume, (pa_volume_t) -diff * PA_VOLUME_NORM / 100);
+ pa_operation_unref (pa_context_set_sink_volume_by_name (ctx->context,
+ sink->name, &volume, on_pa_finish, ctx));
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static int g_terminal_lines;
+static int g_terminal_columns;
+
+static void
+update_screen_size (void)
+{
+ struct winsize size;
+ if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
+ {
+ char *row = getenv ("LINES");
+ char *col = getenv ("COLUMNS");
+ unsigned long tmp;
+ g_terminal_lines =
+ (row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row;
+ g_terminal_columns =
+ (col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col;
+ }
+}
+
+static void
+on_redraw (struct app_context *ctx)
+{
+ poller_idle_reset (&ctx->redraw_event);
+ update_screen_size ();
+
+ printf ("\x1b[H"); // Cursor to home
+ printf ("\x1b[2J"); // Clear the whole screen
+
+ // TODO: see if we can reduce flickering. Buffering doesn't help much.
+ // TODO: try not to write more lines than g_terminal_lines for starters
+ if (ctx->failed)
+ {
+ printf ("PulseAudio connection failed, reconnect in progress.\r\n");
+ return;
+ }
+
+ LIST_FOR_EACH (struct sink, sink, ctx->sinks)
+ {
+ if (ctx->default_sink && !strcmp (sink->name, ctx->default_sink))
+ printf ("\x1b[1m");
+ if (sink->index == ctx->selected_sink && ctx->selected_port < 0)
+ printf ("\x1b[7m");
+ // TODO: erase until end of line with current attributes?
+
+ char *volume = make_volume_status (sink);
+ printf ("%s (%s", sink->description, volume);
+ free (volume);
+
+ char *inputs = make_inputs_status (ctx, sink);
+ if (inputs) printf (", %s", inputs);
+ free (inputs);
+
+ printf (")\x1b[m\r\n");
+
+ for (size_t i = 0; i < sink->ports_len; i++)
+ {
+ struct port *port = sink->ports + i;
+ printf (" ");
+
+ if (!strcmp (port->name, sink->port_active))
+ printf ("\x1b[1m");
+ if (sink->index == ctx->selected_sink
+ && ctx->selected_port == (ssize_t) i)
+ printf ("\x1b[7m");
+ // TODO: erase until end of line with current attributes?
+
+ printf ("%s", port->description);
+ if (port->available == PA_PORT_AVAILABLE_YES)
+ printf (" (plugged in)");
+ else if (port->available == PA_PORT_AVAILABLE_NO)
+ printf (" (unplugged)");
+
+ printf ("\x1b[m\r\n");
+ }
+ }
+ fflush (stdout);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+enum action
+{
+ ACTION_NONE, ACTION_UP, ACTION_DOWN, ACTION_SELECT,
+ ACTION_VOLUP, ACTION_VOLDOWN, ACTION_MUTE, ACTION_QUIT
+};
+
+static void
+on_action (struct app_context *ctx, enum action action)
+{
+ poller_idle_set (&ctx->redraw_event);
+
+ struct sink *sink = NULL;
+ LIST_FOR_EACH (struct sink, iter, ctx->sinks)
+ if (iter->index == ctx->selected_sink)
+ sink = iter;
+
+ switch (action)
+ {
+ case ACTION_UP:
+ if (!sink)
+ break;
+
+ if (ctx->selected_port >= 0)
+ ctx->selected_port--;
+ else if (sink->prev)
+ {
+ ctx->selected_sink = sink->prev->index;
+ ctx->selected_port = sink->prev->ports_len - 1;
+ }
+ else if (ctx->sinks_tail)
+ {
+ ctx->selected_sink = ctx->sinks_tail->index;
+ ctx->selected_port = ctx->sinks_tail->ports_len - 1;
+ }
+ break;
+ case ACTION_DOWN:
+ if (!sink)
+ break;
+
+ if (ctx->selected_port + 1 < (ssize_t) sink->ports_len)
+ ctx->selected_port++;
+ else if (sink->next)
+ {
+ ctx->selected_sink = sink->next->index;
+ ctx->selected_port = -1;
+ }
+ else if (ctx->sinks)
+ {
+ ctx->selected_sink = ctx->sinks->index;
+ ctx->selected_port = -1;
+ }
+ break;
+ case ACTION_SELECT:
+ if (!sink)
+ break;
+
+ if (ctx->selected_port != -1)
+ sink_switch_port (ctx, sink, ctx->selected_port);
+
+ if (strcmp (ctx->default_sink, sink->name))
+ {
+ pa_operation_unref (pa_context_set_default_sink (ctx->context,
+ sink->name, on_pa_finish, ctx));
+ }
+ LIST_FOR_EACH (struct sink_input, input, ctx->inputs)
+ {
+ if (input->sink == sink->index)
+ continue;
+ pa_operation_unref (pa_context_move_sink_input_by_index
+ (ctx->context, input->index, sink->index, on_pa_finish, ctx));
+ }
+ break;
+
+ case ACTION_VOLUP:
+ if (sink)
+ sink_set_volume (ctx, sink, 5);
+ break;
+ case ACTION_VOLDOWN:
+ if (sink)
+ sink_set_volume (ctx, sink, -5);
+ break;
+ case ACTION_MUTE:
+ if (sink)
+ sink_mute (ctx, sink);
+ break;
+
+ case ACTION_QUIT:
+ poller_pa_quit (ctx->api, 0);
+ case ACTION_NONE:
+ break;
+ }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct key_handler
+{
+ const char *keyseq;
+ enum action action;
+}
+g_key_handlers[] =
+{
+ // In local mode, xterm, st, rxvt-unicode and VTE all use these,
+ // which copy ANSI/ISO/ECMA codes for cursor movement;
+ // we don't enable keypad mode which would break that
+ { "\x1b[A", ACTION_UP },
+ { "\x1b[B", ACTION_DOWN },
+
+ { "k", ACTION_UP },
+ { "j", ACTION_DOWN },
+ { "\r", ACTION_SELECT },
+ { "\x1b[5~", ACTION_VOLUP },
+ { "\x1b[6~", ACTION_VOLDOWN },
+ { "m", ACTION_MUTE },
+ { "q", ACTION_QUIT },
+ { "\x1b", ACTION_QUIT },
+ { NULL, ACTION_NONE },
+};
+
+static void
+handle_key (struct app_context *ctx, const char *keyseq, size_t len)
+{
+ for (const struct key_handler *i = g_key_handlers; i->keyseq; i++)
+ if (strlen (i->keyseq) == len && memcmp (i->keyseq, keyseq, len) == 0)
+ {
+ on_action (ctx, i->action);
+ return;
+ }
+
+#if 0
+ // Development tool
+ for (size_t i = 0; i < len; i++)
+ {
+ if ((unsigned char) keyseq[i] < 32 || keyseq[i] == 127)
+ printf ("^%c", '@' + keyseq[i]);
+ else
+ putchar (keyseq[i]);
+ }
+ printf ("\r\n");
+#endif
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+/// Match a terminal key sequence roughly following the ABNF syntax below and
+/// return its length on a full, unambigious match. Partial, ambiguous matches
+/// are returned as negative numbers. Returns zero iff "len" is zero.
+///
+/// match = alt-key / key
+/// alt-key = ESC key
+/// key = csi-seq / ss3-seq / multibyte-character / OCTET
+/// csi-seq = ESC '[' *%x30-3F (%x00-2F / %x40-FF)
+/// ss3-seq = ESC 'O' OCTET
+static int
+read_key_sequence (const char *buf, size_t len)
+{
+ const char *p = buf, *end = buf + len;
+ if (p < end && *p == 27)
+ p++;
+ if (p < end && *p == 27)
+ p++;
+
+ int escapes = p - buf;
+ if (p == end)
+ return -escapes;
+
+ // CSI and SS3 escape sequences are accepted in a very generic format
+ // because they don't need to follow ECMA-48 and e.g. urxvt ends shifted
+ // keys with $ (an intermediate character) -- best effort
+ if (escapes)
+ {
+ if (*p == '[')
+ {
+ while (++p < end)
+ if (*p < 0x30 || *p > 0x3F)
+ return ++p - buf;
+ return -escapes;
+ }
+ if (*p == 'O')
+ {
+ if (++p < end)
+ return ++p - buf;
+ return -escapes;
+ }
+ if (escapes == 2)
+ return -escapes;
+ }
+
+ // Shift state encodings aren't going to work, though anything else should
+ mbstate_t mb = {};
+ int length = mbrlen (p, end - p, &mb);
+ if (length == -2)
+ return -escapes - 1;
+ if (length == -1 || !length)
+ return escapes + 1;
+ return escapes + length;
+}
+
+static void
+tty_process_buffer (struct app_context *ctx)
+{
+ struct str *buf = &ctx->tty_input_buffer;
+ const char *p = buf->str, *end = p + buf->len;
+ for (int res = 0; (res = read_key_sequence (p, end - p)) > 0; p += res)
+ handle_key (ctx, p, res);
+ str_remove_slice (buf, 0, p - buf->str);
+
+ poller_timer_reset (&ctx->tty_timer);
+ if (buf->len)
+ poller_timer_set (&ctx->tty_timer, 100);
+}
+
+static void
+on_tty_timeout (struct app_context *ctx)
+{
+ struct str *buf = &ctx->tty_input_buffer;
+ int res = abs (read_key_sequence (buf->str, buf->len));
+ if (res)
+ {
+ handle_key (ctx, buf->str, res);
+ str_remove_slice (buf, 0, res);
+ }
+
+ // The ambiguous sequence may explode into several other sequences
+ tty_process_buffer (ctx);
+}
+
+static void
+on_tty_readable (const struct pollfd *fd, struct app_context *ctx)
+{
+ if (fd->revents & ~(POLLIN | POLLHUP | POLLERR))
+ print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
+
+ struct str *buf = &ctx->tty_input_buffer;
+ str_reserve (buf, 1);
+
+ int res = read (fd->fd, buf->str + buf->len, buf->alloc - buf->len - 1);
+ if (res > 0)
+ {
+ buf->str[buf->len += res] = '\0';
+ tty_process_buffer (ctx);
+ }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct termios g_saved_termios;
+
+static void
+tty_reset (void)
+{
+ printf ("\x1b[?1049l"); // Exit CA mode (alternate screen)
+ printf ("\x1b[?25h"); // Show cursor
+ fflush (stdout);
+
+ tcsetattr (STDIN_FILENO, TCSAFLUSH, &g_saved_termios);
+}
+
+static bool
+tty_start (void)
+{
+ if (tcgetattr (STDIN_FILENO, &g_saved_termios) < 0)
+ return false;
+
+ struct termios request = g_saved_termios, result = {};
+ request.c_cc[VMIN] = request.c_cc[VTIME] = 0;
+ request.c_lflag &= ~(ECHO | ICANON);
+ request.c_iflag &= ~(ICRNL);
+ request.c_oflag &= ~(OPOST);
+
+ atexit (tty_reset);
+ if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &request) < 0
+ || tcgetattr (STDIN_FILENO, &result) < 0
+ || memcmp (request.c_cc, result.c_cc, sizeof request.c_cc)
+ || request.c_lflag != result.c_lflag
+ || request.c_iflag != result.c_iflag
+ || request.c_oflag != result.c_oflag)
+ return false;
+
+ printf ("\x1b[?1049h"); // Enter CA mode (alternate screen)
+ printf ("\x1b[?25l"); // Hide cursor
+ fflush (stdout);
+ return true;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static int g_signal_pipe[2]; ///< A pipe used to signal... signals
+static struct poller_fd g_signal_event; ///< Signal pipe is readable
+
+static void
+on_signal (int sig)
+{
+ char id = sig;
+
+ // Assuming that the pipe won't normally overflow (16 pages on Linux)
+ int original_errno = errno;
+ if (write (g_signal_pipe[PIPE_WRITE], &id, 1) == -1)
+ soft_assert (errno == EAGAIN);
+ errno = original_errno;
+}
+
+static void
+on_signal_pipe_readable (const struct pollfd *pfd, struct app_context *ctx)
+{
+ char id = 0;
+ (void) read (pfd->fd, &id, 1);
+
+ if (id == SIGINT || id == SIGTERM || id == SIGHUP)
+ poller_pa_quit (ctx->api, 0);
+ else if (id == SIGWINCH)
+ poller_idle_set (&ctx->redraw_event);
+ else
+ hard_assert (!"unhandled signal");
+}
+
+static void
+setup_signal_handlers (struct app_context *ctx)
+{
+ (void) signal (SIGPIPE, SIG_IGN);
+
+ if (pipe (g_signal_pipe) == -1)
+ exit_fatal ("%s: %s", "pipe", strerror (errno));
+
+ set_cloexec (g_signal_pipe[PIPE_READ]);
+ set_cloexec (g_signal_pipe[PIPE_WRITE]);
+
+ // So that the pipe cannot overflow; it would make write() block within
+ // the signal handler, which is something we really don't want to happen.
+ // The same holds true for read().
+ set_blocking (g_signal_pipe[PIPE_READ], false);
+ set_blocking (g_signal_pipe[PIPE_WRITE], false);
+
+ struct sigaction sa;
+ sa.sa_flags = SA_RESTART;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_handler = on_signal;
+ if (sigaction (SIGINT, &sa, NULL) == -1
+ || sigaction (SIGTERM, &sa, NULL) == -1
+ || sigaction (SIGHUP, &sa, NULL) == -1
+ || sigaction (SIGWINCH, &sa, NULL) == -1)
+ print_error ("%s: %s", "sigaction", strerror (errno));
+
+ g_signal_event = poller_fd_make (&ctx->poller, g_signal_pipe[PIPE_READ]);
+ g_signal_event.dispatcher = (poller_fd_fn) on_signal_pipe_readable;
+ g_signal_event.user_data = ctx;
+ poller_fd_set (&g_signal_event, POLLIN);
+}
+
+static void
+poller_timer_init_and_set (struct poller_timer *self, struct poller *poller,
+ poller_timer_fn cb, void *user_data)
+{
+ *self = poller_timer_make (poller);
+ self->dispatcher = cb;
+ self->user_data = user_data;
+
+ poller_timer_set (self, 0);
+}
+
+#ifdef TESTING
+static void
+test_read_key_sequence (void)
+{
+ static struct
+ {
+ const char *buffer; ///< Terminal input buffer
+ int expected; ///< Expected parse result
+ }
+ cases[] =
+ {
+ { "", 0 }, { "\x1b[A_", 3 }, { "\x1b\x1b[", -2 },
+ { "Ř", 2 }, { "\x1bOA_", 3 }, { "\x1b\x1bO", -2 },
+ };
+
+ setlocale (LC_CTYPE, "");
+ for (size_t i = 0; i < N_ELEMENTS (cases); i++)
+ hard_assert (read_key_sequence (cases[i].buffer,
+ strlen (cases[i].buffer)) == cases[i].expected);
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct test test;
+ test_init (&test, argc, argv);
+ test_add_simple (&test, "/read-key-sequence", NULL, test_read_key_sequence);
+ return test_run (&test);
+}
+
+#define main main_shadowed
+#endif // TESTING
+
+int
+main (int argc, char *argv[])
+{
+ static const struct opt opts[] =
+ {
+ { 'd', "debug", NULL, 0, "run in debug mode" },
+ { 'h', "help", NULL, 0, "display this help and exit" },
+ { 'V', "version", NULL, 0, "output version information and exit" },
+ { 0, NULL, NULL, 0, NULL }
+ };
+
+ struct opt_handler oh =
+ opt_handler_make (argc, argv, opts, NULL, "Switch PA outputs.");
+
+ int c;
+ while ((c = opt_handler_get (&oh)) != -1)
+ switch (c)
+ {
+ case 'd':
+ g_debug_mode = true;
+ break;
+ case 'h':
+ opt_handler_usage (&oh, stdout);
+ exit (EXIT_SUCCESS);
+ case 'V':
+ printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
+ exit (EXIT_SUCCESS);
+ default:
+ print_error ("wrong options");
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ opt_handler_free (&oh);
+
+ if (!isatty (STDIN_FILENO))
+ print_fatal ("input is not a terminal");
+ if (!isatty (STDOUT_FILENO))
+ print_fatal ("output is not a terminal");
+
+ setlocale (LC_CTYPE, "");
+ // PulseAudio uses UTF-8, let's avoid encoding conversions
+ if (strcasecmp (nl_langinfo (CODESET), "UTF-8"))
+ print_fatal ("UTF-8 encoding required");
+ if (setvbuf (stdout, NULL, _IOLBF, 0) || !tty_start ())
+ print_fatal ("terminal initialization failed");
+
+ // TODO: we will need a logging function aware of our rendering
+ g_log_message_real = log_message_custom;
+
+ struct app_context ctx;
+ app_context_init (&ctx);
+ setup_signal_handlers (&ctx);
+
+ ctx.redraw_event = poller_idle_make (&ctx.poller);
+ ctx.redraw_event.dispatcher = (poller_idle_fn) on_redraw;
+ ctx.redraw_event.user_data = &ctx;
+ poller_idle_set (&ctx.redraw_event);
+
+ ctx.tty_event = poller_fd_make (&ctx.poller, STDIN_FILENO);
+ ctx.tty_event.dispatcher = (poller_fd_fn) on_tty_readable;
+ ctx.tty_event.user_data = &ctx;
+ poller_fd_set (&ctx.tty_event, POLLIN);
+
+ ctx.tty_timer = poller_timer_make (&ctx.poller);
+ ctx.tty_timer.dispatcher = (poller_timer_fn) on_tty_timeout;
+ ctx.tty_timer.user_data = &ctx;
+
+ poller_timer_init_and_set (&ctx.make_context, &ctx.poller,
+ on_make_context, &ctx);
+
+ int status = poller_pa_run (ctx.api);
+ app_context_free (&ctx);
+ return status;
+}
diff --git a/poller-pa.c b/poller-pa.c
new file mode 100644
index 0000000..08b5ac0
--- /dev/null
+++ b/poller-pa.c
@@ -0,0 +1,361 @@
+/*
+ * pa.c: PulseAudio mainloop abstraction
+ *
+ * Copyright (c) 2016 - 2017, Přemysl Janouch <p@janouch.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include <pulse/mainloop.h>
+
+// --- PulseAudio mainloop abstraction -----------------------------------------
+
+struct pa_io_event
+{
+ LIST_HEADER (pa_io_event)
+
+ pa_mainloop_api *api; ///< Parent structure
+ struct poller_fd fd; ///< Underlying FD event
+
+ pa_io_event_cb_t dispatch; ///< Dispatcher
+ pa_io_event_destroy_cb_t free; ///< Destroyer
+ void *user_data; ///< User data
+};
+
+struct pa_time_event
+{
+ LIST_HEADER (pa_time_event)
+
+ pa_mainloop_api *api; ///< Parent structure
+ struct poller_timer timer; ///< Underlying timer event
+
+ pa_time_event_cb_t dispatch; ///< Dispatcher
+ pa_time_event_destroy_cb_t free; ///< Destroyer
+ void *user_data; ///< User data
+};
+
+struct pa_defer_event
+{
+ LIST_HEADER (pa_defer_event)
+
+ pa_mainloop_api *api; ///< Parent structure
+ struct poller_idle idle; ///< Underlying idle event
+
+ pa_defer_event_cb_t dispatch; ///< Dispatcher
+ pa_defer_event_destroy_cb_t free; ///< Destroyer
+ void *user_data; ///< User data
+};
+
+struct poller_pa
+{
+ struct poller *poller; ///< The underlying event loop
+ int result; ///< Result on quit
+ bool running; ///< Not quitting
+
+ pa_io_event *io_list; ///< I/O events
+ pa_time_event *time_list; ///< Timer events
+ pa_defer_event *defer_list; ///< Deferred events
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static short
+poller_pa_flags_to_events (pa_io_event_flags_t flags)
+{
+ short result = 0;
+ if (flags & PA_IO_EVENT_ERROR) result |= POLLERR;
+ if (flags & PA_IO_EVENT_HANGUP) result |= POLLHUP;
+ if (flags & PA_IO_EVENT_INPUT) result |= POLLIN;
+ if (flags & PA_IO_EVENT_OUTPUT) result |= POLLOUT;
+ return result;
+}
+
+static pa_io_event_flags_t
+poller_pa_events_to_flags (short events)
+{
+ pa_io_event_flags_t result = 0;
+ if (events & POLLERR) result |= PA_IO_EVENT_ERROR;
+ if (events & POLLHUP) result |= PA_IO_EVENT_HANGUP;
+ if (events & POLLIN) result |= PA_IO_EVENT_INPUT;
+ if (events & POLLOUT) result |= PA_IO_EVENT_OUTPUT;
+ return result;
+}
+
+static struct timeval
+poller_pa_get_current_time (void)
+{
+ struct timeval tv;
+#ifdef _POSIX_TIMERS
+ struct timespec tp;
+ hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
+ tv.tv_sec = tp.tv_sec;
+ tv.tv_usec = tp.tv_nsec / 1000;
+#else
+ gettimeofday (&tv, NULL);
+#endif
+ return tv;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+poller_pa_io_dispatcher (const struct pollfd *pfd, void *user_data)
+{
+ pa_io_event *self = user_data;
+ self->dispatch (self->api, self,
+ pfd->fd, poller_pa_events_to_flags (pfd->revents), self->user_data);
+}
+
+static void
+poller_pa_io_enable (pa_io_event *self, pa_io_event_flags_t events)
+{
+ struct poller_fd *fd = &self->fd;
+ if (events)
+ poller_fd_set (fd, poller_pa_flags_to_events (events));
+ else
+ poller_fd_reset (fd);
+}
+
+static pa_io_event *
+poller_pa_io_new (pa_mainloop_api *api, int fd_, pa_io_event_flags_t events,
+ pa_io_event_cb_t cb, void *userdata)
+{
+ pa_io_event *self = xcalloc (1, sizeof *self);
+ self->api = api;
+ self->dispatch = cb;
+ self->user_data = userdata;
+
+ struct poller_pa *data = api->userdata;
+ self->fd = poller_fd_make (data->poller, fd_);
+ self->fd.user_data = self;
+ self->fd.dispatcher = poller_pa_io_dispatcher;
+
+ // FIXME: under x2go PA tries to register twice for the same FD,
+ // which fails with our curent poller implementation;
+ // we could maintain a list of { poller_fd, listeners } structures;
+ // or maybe we're doing something wrong, which is yet to be determined
+ poller_pa_io_enable (self, events);
+ LIST_PREPEND (data->io_list, self);
+ return self;
+}
+
+static void
+poller_pa_io_free (pa_io_event *self)
+{
+ if (self->free)
+ self->free (self->api, self, self->user_data);
+
+ struct poller_pa *data = self->api->userdata;
+ poller_fd_reset (&self->fd);
+ LIST_UNLINK (data->io_list, self);
+ free (self);
+}
+
+static void
+poller_pa_io_set_destroy (pa_io_event *self, pa_io_event_destroy_cb_t cb)
+{
+ self->free = cb;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+poller_pa_time_dispatcher (void *user_data)
+{
+ pa_time_event *self = user_data;
+ // XXX: the meaning of the time argument is undocumented,
+ // so let's just put current Unix time in there
+ struct timeval now = poller_pa_get_current_time ();
+ self->dispatch (self->api, self, &now, self->user_data);
+}
+
+static void
+poller_pa_time_restart (pa_time_event *self, const struct timeval *tv)
+{
+ struct poller_timer *timer = &self->timer;
+ if (tv)
+ {
+ struct timeval now = poller_pa_get_current_time ();
+ poller_timer_set (timer,
+ (tv->tv_sec - now.tv_sec) * 1000 +
+ (tv->tv_usec - now.tv_usec) / 1000);
+ }
+ else
+ poller_timer_reset (timer);
+}
+
+static pa_time_event *
+poller_pa_time_new (pa_mainloop_api *api, const struct timeval *tv,
+ pa_time_event_cb_t cb, void *userdata)
+{
+ pa_time_event *self = xcalloc (1, sizeof *self);
+ self->api = api;
+ self->dispatch = cb;
+ self->user_data = userdata;
+
+ struct poller_pa *data = api->userdata;
+ self->timer = poller_timer_make (data->poller);
+ self->timer.user_data = self;
+ self->timer.dispatcher = poller_pa_time_dispatcher;
+
+ poller_pa_time_restart (self, tv);
+ LIST_PREPEND (data->time_list, self);
+ return self;
+}
+
+static void
+poller_pa_time_free (pa_time_event *self)
+{
+ if (self->free)
+ self->free (self->api, self, self->user_data);
+
+ struct poller_pa *data = self->api->userdata;
+ poller_timer_reset (&self->timer);
+ LIST_UNLINK (data->time_list, self);
+ free (self);
+}
+
+static void
+poller_pa_time_set_destroy (pa_time_event *self, pa_time_event_destroy_cb_t cb)
+{
+ self->free = cb;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+poller_pa_defer_dispatcher (void *user_data)
+{
+ pa_defer_event *self = user_data;
+ self->dispatch (self->api, self, self->user_data);
+}
+
+static pa_defer_event *
+poller_pa_defer_new (pa_mainloop_api *api,
+ pa_defer_event_cb_t cb, void *userdata)
+{
+ pa_defer_event *self = xcalloc (1, sizeof *self);
+ self->api = api;
+ self->dispatch = cb;
+ self->user_data = userdata;
+
+ struct poller_pa *data = api->userdata;
+ self->idle = poller_idle_make (data->poller);
+ self->idle.user_data = self;
+ self->idle.dispatcher = poller_pa_defer_dispatcher;
+
+ poller_idle_set (&self->idle);
+ LIST_PREPEND (data->defer_list, self);
+ return self;
+}
+
+static void
+poller_pa_defer_enable (pa_defer_event *self, int enable)
+{
+ struct poller_idle *idle = &self->idle;
+ if (enable)
+ poller_idle_set (idle);
+ else
+ poller_idle_reset (idle);
+}
+
+static void
+poller_pa_defer_free (pa_defer_event *self)
+{
+ if (self->free)
+ self->free (self->api, self, self->user_data);
+
+ struct poller_pa *data = self->api->userdata;
+ poller_idle_reset (&self->idle);
+ LIST_UNLINK (data->defer_list, self);
+ free (self);
+}
+
+static void
+poller_pa_defer_set_destroy (pa_defer_event *self,
+ pa_defer_event_destroy_cb_t cb)
+{
+ self->free = cb;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+poller_pa_quit (pa_mainloop_api *api, int retval)
+{
+ struct poller_pa *data = api->userdata;
+ data->result = retval;
+ data->running = false;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct pa_mainloop_api g_poller_pa_template =
+{
+ .io_new = poller_pa_io_new,
+ .io_enable = poller_pa_io_enable,
+ .io_free = poller_pa_io_free,
+ .io_set_destroy = poller_pa_io_set_destroy,
+
+ .time_new = poller_pa_time_new,
+ .time_restart = poller_pa_time_restart,
+ .time_free = poller_pa_time_free,
+ .time_set_destroy = poller_pa_time_set_destroy,
+
+ .defer_new = poller_pa_defer_new,
+ .defer_enable = poller_pa_defer_enable,
+ .defer_free = poller_pa_defer_free,
+ .defer_set_destroy = poller_pa_defer_set_destroy,
+
+ .quit = poller_pa_quit,
+};
+
+static struct pa_mainloop_api *
+poller_pa_new (struct poller *self)
+{
+ struct poller_pa *data = xcalloc (1, sizeof *data);
+ data->poller = self;
+
+ struct pa_mainloop_api *api = xmalloc (sizeof *api);
+ *api = g_poller_pa_template;
+ api->userdata = data;
+ return api;
+}
+
+static void
+poller_pa_destroy (struct pa_mainloop_api *api)
+{
+ struct poller_pa *data = api->userdata;
+
+ LIST_FOR_EACH (pa_io_event, iter, data->io_list)
+ poller_pa_io_free (iter);
+ LIST_FOR_EACH (pa_time_event, iter, data->time_list)
+ poller_pa_time_free (iter);
+ LIST_FOR_EACH (pa_defer_event, iter, data->defer_list)
+ poller_pa_defer_free (iter);
+
+ free (data);
+ free (api);
+}
+
+/// Since our poller API doesn't care much about continuous operation,
+/// we need to provide that in the PulseAudio abstraction itself
+static int
+poller_pa_run (struct pa_mainloop_api *api)
+{
+ struct poller_pa *data = api->userdata;
+ data->running = true;
+ while (data->running)
+ poller_run (data->poller);
+ return data->result;
+}
diff --git a/wmstatus.c b/wmstatus.c
index ed1dda2..a154586 100644
--- a/wmstatus.c
+++ b/wmstatus.c
@@ -26,6 +26,7 @@
#undef PROGRAM_NAME
#define PROGRAM_NAME "wmstatus"
#include "liberty/liberty.c"
+#include "poller-pa.c"
#include <dirent.h>
#include <spawn.h>
@@ -36,7 +37,6 @@
#include <X11/XKBlib.h>
#include <X11/extensions/sync.h>
-#include <pulse/mainloop.h>
#include <pulse/context.h>
#include <pulse/error.h>
#include <pulse/introspect.h>
@@ -61,348 +61,6 @@ log_message_custom (void *user_data, const char *quote, const char *fmt,
fputs ("\n", stream);
}
-// --- PulseAudio mainloop abstraction -----------------------------------------
-
-struct pa_io_event
-{
- LIST_HEADER (pa_io_event)
-
- pa_mainloop_api *api; ///< Parent structure
- struct poller_fd fd; ///< Underlying FD event
-
- pa_io_event_cb_t dispatch; ///< Dispatcher
- pa_io_event_destroy_cb_t free; ///< Destroyer
- void *user_data; ///< User data
-};
-
-struct pa_time_event
-{
- LIST_HEADER (pa_time_event)
-
- pa_mainloop_api *api; ///< Parent structure
- struct poller_timer timer; ///< Underlying timer event
-
- pa_time_event_cb_t dispatch; ///< Dispatcher
- pa_time_event_destroy_cb_t free; ///< Destroyer
- void *user_data; ///< User data
-};
-
-struct pa_defer_event
-{
- LIST_HEADER (pa_defer_event)
-
- pa_mainloop_api *api; ///< Parent structure
- struct poller_idle idle; ///< Underlying idle event
-
- pa_defer_event_cb_t dispatch; ///< Dispatcher
- pa_defer_event_destroy_cb_t free; ///< Destroyer
- void *user_data; ///< User data
-};
-
-struct poller_pa
-{
- struct poller *poller; ///< The underlying event loop
- int result; ///< Result on quit
- bool running; ///< Not quitting
-
- pa_io_event *io_list; ///< I/O events
- pa_time_event *time_list; ///< Timer events
- pa_defer_event *defer_list; ///< Deferred events
-};
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static short
-poller_pa_flags_to_events (pa_io_event_flags_t flags)
-{
- short result = 0;
- if (flags & PA_IO_EVENT_ERROR) result |= POLLERR;
- if (flags & PA_IO_EVENT_HANGUP) result |= POLLHUP;
- if (flags & PA_IO_EVENT_INPUT) result |= POLLIN;
- if (flags & PA_IO_EVENT_OUTPUT) result |= POLLOUT;
- return result;
-}
-
-static pa_io_event_flags_t
-poller_pa_events_to_flags (short events)
-{
- pa_io_event_flags_t result = 0;
- if (events & POLLERR) result |= PA_IO_EVENT_ERROR;
- if (events & POLLHUP) result |= PA_IO_EVENT_HANGUP;
- if (events & POLLIN) result |= PA_IO_EVENT_INPUT;
- if (events & POLLOUT) result |= PA_IO_EVENT_OUTPUT;
- return result;
-}
-
-static struct timeval
-poller_pa_get_current_time (void)
-{
- struct timeval tv;
-#ifdef _POSIX_TIMERS
- struct timespec tp;
- hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
- tv.tv_sec = tp.tv_sec;
- tv.tv_usec = tp.tv_nsec / 1000;
-#else
- gettimeofday (&tv, NULL);
-#endif
- return tv;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-poller_pa_io_dispatcher (const struct pollfd *pfd, void *user_data)
-{
- pa_io_event *self = user_data;
- self->dispatch (self->api, self,
- pfd->fd, poller_pa_events_to_flags (pfd->revents), self->user_data);
-}
-
-static void
-poller_pa_io_enable (pa_io_event *self, pa_io_event_flags_t events)
-{
- struct poller_fd *fd = &self->fd;
- if (events)
- poller_fd_set (fd, poller_pa_flags_to_events (events));
- else
- poller_fd_reset (fd);
-}
-
-static pa_io_event *
-poller_pa_io_new (pa_mainloop_api *api, int fd_, pa_io_event_flags_t events,
- pa_io_event_cb_t cb, void *userdata)
-{
- pa_io_event *self = xcalloc (1, sizeof *self);
- self->api = api;
- self->dispatch = cb;
- self->user_data = userdata;
-
- struct poller_pa *data = api->userdata;
- self->fd = poller_fd_make (data->poller, fd_);
- self->fd.user_data = self;
- self->fd.dispatcher = poller_pa_io_dispatcher;
-
- // FIXME: under x2go PA tries to register twice for the same FD,
- // which fails with our curent poller implementation;
- // we could maintain a list of { poller_fd, listeners } structures;
- // or maybe we're doing something wrong, which is yet to be determined
- poller_pa_io_enable (self, events);
- LIST_PREPEND (data->io_list, self);
- return self;
-}
-
-static void
-poller_pa_io_free (pa_io_event *self)
-{
- if (self->free)
- self->free (self->api, self, self->user_data);
-
- struct poller_pa *data = self->api->userdata;
- poller_fd_reset (&self->fd);
- LIST_UNLINK (data->io_list, self);
- free (self);
-}
-
-static void
-poller_pa_io_set_destroy (pa_io_event *self, pa_io_event_destroy_cb_t cb)
-{
- self->free = cb;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-poller_pa_time_dispatcher (void *user_data)
-{
- pa_time_event *self = user_data;
- // XXX: the meaning of the time argument is undocumented,
- // so let's just put current Unix time in there
- struct timeval now = poller_pa_get_current_time ();
- self->dispatch (self->api, self, &now, self->user_data);
-}
-
-static void
-poller_pa_time_restart (pa_time_event *self, const struct timeval *tv)
-{
- struct poller_timer *timer = &self->timer;
- if (tv)
- {
- struct timeval now = poller_pa_get_current_time ();
- poller_timer_set (timer,
- (tv->tv_sec - now.tv_sec) * 1000 +
- (tv->tv_usec - now.tv_usec) / 1000);
- }
- else
- poller_timer_reset (timer);
-}
-
-static pa_time_event *
-poller_pa_time_new (pa_mainloop_api *api, const struct timeval *tv,
- pa_time_event_cb_t cb, void *userdata)
-{
- pa_time_event *self = xcalloc (1, sizeof *self);
- self->api = api;
- self->dispatch = cb;
- self->user_data = userdata;
-
- struct poller_pa *data = api->userdata;
- self->timer = poller_timer_make (data->poller);
- self->timer.user_data = self;
- self->timer.dispatcher = poller_pa_time_dispatcher;
-
- poller_pa_time_restart (self, tv);
- LIST_PREPEND (data->time_list, self);
- return self;
-}
-
-static void
-poller_pa_time_free (pa_time_event *self)
-{
- if (self->free)
- self->free (self->api, self, self->user_data);
-
- struct poller_pa *data = self->api->userdata;
- poller_timer_reset (&self->timer);
- LIST_UNLINK (data->time_list, self);
- free (self);
-}
-
-static void
-poller_pa_time_set_destroy (pa_time_event *self, pa_time_event_destroy_cb_t cb)
-{
- self->free = cb;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-poller_pa_defer_dispatcher (void *user_data)
-{
- pa_defer_event *self = user_data;
- self->dispatch (self->api, self, self->user_data);
-}
-
-static pa_defer_event *
-poller_pa_defer_new (pa_mainloop_api *api,
- pa_defer_event_cb_t cb, void *userdata)
-{
- pa_defer_event *self = xcalloc (1, sizeof *self);
- self->api = api;
- self->dispatch = cb;
- self->user_data = userdata;
-
- struct poller_pa *data = api->userdata;
- self->idle = poller_idle_make (data->poller);
- self->idle.user_data = self;
- self->idle.dispatcher = poller_pa_defer_dispatcher;
-
- poller_idle_set (&self->idle);
- LIST_PREPEND (data->defer_list, self);
- return self;
-}
-
-static void
-poller_pa_defer_enable (pa_defer_event *self, int enable)
-{
- struct poller_idle *idle = &self->idle;
- if (enable)
- poller_idle_set (idle);
- else
- poller_idle_reset (idle);
-}
-
-static void
-poller_pa_defer_free (pa_defer_event *self)
-{
- if (self->free)
- self->free (self->api, self, self->user_data);
-
- struct poller_pa *data = self->api->userdata;
- poller_idle_reset (&self->idle);
- LIST_UNLINK (data->defer_list, self);
- free (self);
-}
-
-static void
-poller_pa_defer_set_destroy (pa_defer_event *self,
- pa_defer_event_destroy_cb_t cb)
-{
- self->free = cb;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-poller_pa_quit (pa_mainloop_api *api, int retval)
-{
- struct poller_pa *data = api->userdata;
- data->result = retval;
- data->running = false;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static struct pa_mainloop_api g_poller_pa_template =
-{
- .io_new = poller_pa_io_new,
- .io_enable = poller_pa_io_enable,
- .io_free = poller_pa_io_free,
- .io_set_destroy = poller_pa_io_set_destroy,
-
- .time_new = poller_pa_time_new,
- .time_restart = poller_pa_time_restart,
- .time_free = poller_pa_time_free,
- .time_set_destroy = poller_pa_time_set_destroy,
-
- .defer_new = poller_pa_defer_new,
- .defer_enable = poller_pa_defer_enable,
- .defer_free = poller_pa_defer_free,
- .defer_set_destroy = poller_pa_defer_set_destroy,
-
- .quit = poller_pa_quit,
-};
-
-static struct pa_mainloop_api *
-poller_pa_new (struct poller *self)
-{
- struct poller_pa *data = xcalloc (1, sizeof *data);
- data->poller = self;
-
- struct pa_mainloop_api *api = xmalloc (sizeof *api);
- *api = g_poller_pa_template;
- api->userdata = data;
- return api;
-}
-
-static void
-poller_pa_destroy (struct pa_mainloop_api *api)
-{
- struct poller_pa *data = api->userdata;
-
- LIST_FOR_EACH (pa_io_event, iter, data->io_list)
- poller_pa_io_free (iter);
- LIST_FOR_EACH (pa_time_event, iter, data->time_list)
- poller_pa_time_free (iter);
- LIST_FOR_EACH (pa_defer_event, iter, data->defer_list)
- poller_pa_defer_free (iter);
-
- free (data);
- free (api);
-}
-
-/// Since our poller API doesn't care much about continuous operation,
-/// we need to provide that in the PulseAudio abstraction itself
-static int
-poller_pa_run (struct pa_mainloop_api *api)
-{
- struct poller_pa *data = api->userdata;
- data->running = true;
- while (data->running)
- poller_run (data->poller);
- return data->result;
-}
-
// --- NUT ---------------------------------------------------------------------
// More or less copied and pasted from the MPD client. This code doesn't even