From a5737096357a4014f67cf23041e0760308b4b517 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 7 May 2017 00:41:20 +0200
Subject: wmstatus: allow running a slave command
For various nonsense that would be hard to implement in C.
We've discovered a few bugs because of this so all's good.
---
liberty | 2 +-
wmstatus.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 204 insertions(+), 11 deletions(-)
diff --git a/liberty b/liberty
index 084e964..17322a3 160000
--- a/liberty
+++ b/liberty
@@ -1 +1 @@
-Subproject commit 084e964286bfcd13ee6a25a2ee35dfba9da1072e
+Subproject commit 17322a368656f99ab25766161c717629da5b6eee
diff --git a/wmstatus.c b/wmstatus.c
index cc1e318..74b8468 100644
--- a/wmstatus.c
+++ b/wmstatus.c
@@ -44,6 +44,8 @@
// --- Utilities ---------------------------------------------------------------
+enum { PIPE_READ, PIPE_WRITE };
+
static void
log_message_custom (void *user_data, const char *quote, const char *fmt,
va_list ap)
@@ -1131,6 +1133,8 @@ static struct simple_config_item g_config_table[] =
// enhanced configuration format and allowing arbitrary per-UPS overrides
{ "nut_load_power", NULL, "ups.realpower.nominal override" },
+ { "command", NULL, "command to run for more info" },
+
{ NULL, NULL, NULL }
};
@@ -1150,6 +1154,15 @@ struct app_context
struct poller_timer make_context; ///< Start PulseAudio communication
struct poller_timer refresh_rest; ///< Refresh unpollable information
+ // Command:
+
+ struct poller_timer command_start; ///< Start the command
+ struct strv command_current; ///< Current output of the command
+ pid_t command_pid; ///< PID of the command process
+ int command_fd; ///< I/O socket
+ struct poller_fd command_event; ///< I/O event
+ struct str command_buffer; ///< Unprocessed input
+
// Hotkeys:
struct poller_fd x_event; ///< X11 event
@@ -1210,6 +1223,12 @@ app_context_init (struct app_context *self)
poller_init (&self->poller);
self->api = poller_pa_new (&self->poller);
+ strv_init (&self->command_current);
+ self->command_pid = -1;
+ self->command_fd = -1;
+ poller_fd_init (&self->command_event, &self->poller, -1);
+ str_init (&self->command_buffer);
+
set_cloexec (ConnectionNumber (self->dpy));
poller_fd_init (&self->x_event, &self->poller,
ConnectionNumber (self->dpy));
@@ -1235,8 +1254,15 @@ app_context_free (struct app_context *self)
if (self->context) pa_context_unref (self->context);
if (self->dpy) XCloseDisplay (self->dpy);
- poller_pa_destroy (self->api);
- poller_free (&self->poller);
+ strv_free (&self->command_current);
+ if (self->command_pid != -1)
+ (void) kill (self->command_pid, SIGTERM);
+ if (self->command_fd != -1)
+ {
+ poller_fd_reset (&self->command_event);
+ xclose (self->command_fd);
+ }
+ str_free (&self->command_buffer);
mpd_client_free (&self->mpd_client);
free (self->mpd_song);
@@ -1247,6 +1273,9 @@ app_context_free (struct app_context *self)
strv_free (&self->sink_ports);
free (self->sink_port_active);
+
+ poller_pa_destroy (self->api);
+ poller_free (&self->poller);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1463,6 +1492,9 @@ refresh_status (struct app_context *ctx)
if (ctx->nut_status) ctx->backend->add (ctx->backend, ctx->nut_status);
if (ctx->layout) ctx->backend->add (ctx->backend, ctx->layout);
+ for (size_t i = 0; i < ctx->command_current.len; i++)
+ ctx->backend->add (ctx->backend, ctx->command_current.vector[i]);
+
char *times = make_time_status ("Week %V, %a %d %b %Y %H:%M %Z");
ctx->backend->add (ctx->backend, times);
free (times);
@@ -1494,6 +1526,108 @@ on_refresh_rest (void *user_data)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static void
+command_queue_start (struct app_context *ctx)
+{
+ poller_timer_set (&ctx->command_start, 30 * 1000);
+}
+
+static void
+on_command_ready (const struct pollfd *pfd, void *user_data)
+{
+ struct app_context *ctx = user_data;
+ struct str *buf = &ctx->command_buffer;
+ enum socket_io_result result = socket_io_try_read (pfd->fd, buf);
+ bool data_have_changed = false;
+
+ size_t end = 0;
+ for (size_t i = 0; i + 1 < buf->len; i++)
+ {
+ if (buf->str[i] != '\n' || buf->str[i + 1] != '\n')
+ continue;
+
+ buf->str[i + 1] = '\0';
+ strv_reset (&ctx->command_current);
+ cstr_split (buf->str + end, "\n", true, &ctx->command_current);
+ end = i + 2;
+ data_have_changed = true;
+ }
+ str_remove_slice (buf, 0, end);
+
+ if (result != SOCKET_IO_OK)
+ {
+ // The pipe may have been closed independently
+ if (ctx->command_pid != -1)
+ (void) kill (ctx->command_pid, SIGTERM);
+
+ poller_fd_reset (&ctx->command_event);
+ xclose (ctx->command_fd);
+ ctx->command_fd = -1;
+ ctx->command_pid = -1;
+
+ // Make it obvious that something's not right here
+ strv_reset (&ctx->command_current);
+ data_have_changed = true;
+
+ print_error ("external command failed");
+ command_queue_start (ctx);
+ }
+ if (data_have_changed)
+ refresh_status (ctx);
+}
+
+static void
+on_command_start (void *user_data)
+{
+ struct app_context *ctx = user_data;
+ char *command = str_map_find (&ctx->config, "command");
+ if (!command)
+ return;
+
+ int output_pipe[2];
+ if (pipe (output_pipe))
+ {
+ print_error ("%s: %s", "pipe", strerror (errno));
+ command_queue_start (ctx);
+ return;
+ }
+
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init (&actions);
+ posix_spawn_file_actions_adddup2
+ (&actions, output_pipe[PIPE_WRITE], STDOUT_FILENO);
+ posix_spawn_file_actions_addclose (&actions, output_pipe[PIPE_READ]);
+ posix_spawn_file_actions_addclose (&actions, output_pipe[PIPE_WRITE]);
+
+ pid_t pid = -1;
+ char *argv[] = { "sh", "-c", command, NULL };
+ int result = posix_spawnp (&pid, argv[0], &actions, NULL, argv, environ);
+ posix_spawn_file_actions_destroy (&actions);
+
+ set_blocking (output_pipe[PIPE_READ], false);
+ set_cloexec (output_pipe[PIPE_READ]);
+ xclose (output_pipe[PIPE_WRITE]);
+
+ if (result)
+ {
+ xclose (output_pipe[PIPE_READ]);
+ print_error ("%s: %s", "posix_spawnp", strerror (result));
+ command_queue_start (ctx);
+ return;
+ }
+
+ ctx->command_pid = pid;
+ str_reset (&ctx->command_buffer);
+
+ poller_fd_init (&ctx->command_event, &ctx->poller,
+ (ctx->command_fd = output_pipe[PIPE_READ]));
+ ctx->command_event.dispatcher = on_command_ready;
+ ctx->command_event.user_data = ctx;
+ poller_fd_set (&ctx->command_event, POLLIN);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
// Sometimes it's not that easy and there can be repeating entries
static void
mpd_vector_to_map (const struct strv *data, struct str_map *map)
@@ -1652,6 +1786,7 @@ mpd_on_io_hook (void *user_data, bool outgoing, const char *line)
static void
on_mpd_reconnect (void *user_data)
{
+ // FIXME: the user should be able to disable MPD
struct app_context *ctx = user_data;
struct mpd_client *c = &ctx->mpd_client;
@@ -2363,6 +2498,68 @@ grab_keys (struct app_context *ctx)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+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_sigchld (int sig)
+{
+ (void) sig;
+
+ int original_errno = errno;
+ if (write (g_signal_pipe[PIPE_WRITE], "c", 1) == -1)
+ soft_assert (errno == EAGAIN);
+ errno = original_errno;
+}
+
+static void
+on_signal_pipe_readable (const struct pollfd *pfd, struct app_context *ctx)
+{
+ char dummy;
+ (void) read (pfd->fd, &dummy, 1);
+
+ pid_t zombie;
+ while ((zombie = waitpid (-1, NULL, WNOHANG)))
+ {
+ // We want to know when this happens so that we don't accidentally
+ // try to kill an unrelated process on cleanup
+ if (ctx->command_pid == zombie)
+ ctx->command_pid = -1;
+ if (zombie == -1 && errno == ECHILD)
+ return;
+ if (zombie == -1)
+ hard_assert (errno == EINTR);
+ }
+}
+
+static void
+setup_signal_handlers (struct app_context *ctx)
+{
+ 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 | SA_NOCLDSTOP;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_handler = on_sigchld;
+ if (sigaction (SIGCHLD, &sa, NULL) == -1)
+ print_error ("%s: %s", "sigaction", strerror (errno));
+
+ poller_fd_init (&g_signal_event, &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)
@@ -2425,17 +2622,10 @@ main (int argc, char *argv[])
opt_handler_free (&oh);
- // We don't need to retrieve exit statuses of anything, avoid zombies
- struct sigaction sa;
- sa.sa_flags = SA_RESTART | SA_NOCLDWAIT;
- sigemptyset (&sa.sa_mask);
- sa.sa_handler = SIG_IGN;
- if (sigaction (SIGCHLD, &sa, NULL) == -1)
- print_error ("%s: %s", "sigaction", strerror (errno));
-
struct app_context ctx;
app_context_init (&ctx);
ctx.prefix = argc > 1 ? argv[1] : NULL;
+ setup_signal_handlers (&ctx);
struct error *e = NULL;
if (!simple_config_update_from_file (&ctx.config, &e))
@@ -2447,6 +2637,8 @@ main (int argc, char *argv[])
on_make_context, &ctx);
poller_timer_init_and_set (&ctx.refresh_rest, &ctx.poller,
on_refresh_rest, &ctx);
+ poller_timer_init_and_set (&ctx.command_start, &ctx.poller,
+ on_command_start, &ctx);
poller_timer_init_and_set (&ctx.mpd_reconnect, &ctx.poller,
on_mpd_reconnect, &ctx);
poller_timer_init_and_set (&ctx.nut_reconnect, &ctx.poller,
@@ -2465,6 +2657,7 @@ main (int argc, char *argv[])
if (ctx.backend->stop)
ctx.backend->stop (ctx.backend);
+ // We never get here since we don't even handle termination signals
app_context_free (&ctx);
return 0;
}
--
cgit v1.2.3-70-g09d2