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 + * + * 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 +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +// --- 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 + * + * 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 + +// --- 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 #include @@ -36,7 +37,6 @@ #include #include -#include #include #include #include @@ -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