aboutsummaryrefslogtreecommitdiff
path: root/dwmstatus.c
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2016-12-30 11:30:52 +0100
committerPřemysl Janouch <p.janouch@gmail.com>2016-12-30 13:00:20 +0100
commit617bc12ea2842a708bd958a3ed478f663d43fddc (patch)
tree51f3fc3383542ec27ec4957abd3ade8c0afdb626 /dwmstatus.c
parent5e5ccb87482bc0b023a66742dfe9a952bd636452 (diff)
downloaddesktop-tools-617bc12ea2842a708bd958a3ed478f663d43fddc.tar.gz
desktop-tools-617bc12ea2842a708bd958a3ed478f663d43fddc.tar.xz
desktop-tools-617bc12ea2842a708bd958a3ed478f663d43fddc.zip
dwmstatus: add i3bar support, rename to wmstatus
Diffstat (limited to 'dwmstatus.c')
-rw-r--r--dwmstatus.c2283
1 files changed, 0 insertions, 2283 deletions
diff --git a/dwmstatus.c b/dwmstatus.c
deleted file mode 100644
index c19fda5..0000000
--- a/dwmstatus.c
+++ /dev/null
@@ -1,2283 +0,0 @@
-/*
- * dwmstatus.c: simple PulseAudio-enabled dwmstatus
- *
- * Copyright (c) 2015 - 2016, Přemysl Janouch <p.janouch@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * 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 LIBERTY_WANT_ASYNC
-#define LIBERTY_WANT_PROTO_MPD
-
-#define _GNU_SOURCE // openat
-
-#include "config.h"
-#undef PROGRAM_NAME
-#define PROGRAM_NAME "dwmstatus"
-#include "liberty/liberty.c"
-
-#include <dirent.h>
-#include <spawn.h>
-
-#include <X11/Xlib.h>
-#include <X11/keysym.h>
-#include <X11/XF86keysym.h>
-#include <X11/XKBlib.h>
-
-#include <pulse/mainloop.h>
-#include <pulse/context.h>
-#include <pulse/error.h>
-#include <pulse/introspect.h>
-#include <pulse/subscribe.h>
-
-// --- Utilities ---------------------------------------------------------------
-
-static void
-log_message_custom (void *user_data, const char *quote, const char *fmt,
- va_list ap)
-{
- (void) user_data;
- FILE *stream = stdout;
-
- fprintf (stream, PROGRAM_NAME ": ");
- fputs (quote, stream);
- vfprintf (stream, fmt, ap);
- fputs ("\n", stream);
-}
-
-static void
-set_dwm_status (Display *dpy, const char *str)
-{
- print_debug ("setting status to: %s", str);
- XStoreName (dpy, DefaultRootWindow (dpy), str);
- XSync (dpy, False);
-}
-
-// --- 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;
- struct poller_fd *fd = &self->fd;
- poller_fd_init (fd, data->poller, fd_);
- fd->user_data = 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;
- struct poller_timer *timer = &self->timer;
- poller_timer_init (timer, data->poller);
- timer->user_data = 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;
- struct poller_idle *idle = &self->idle;
- poller_idle_init (idle, data->poller);
- idle->user_data = self;
- idle->dispatcher = poller_pa_defer_dispatcher;
-
- poller_idle_set (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
-// deserve much love, the protocol is somehow even worse than MPD's.
-//
-// http://www.networkupstools.org/docs/developer-guide.chunked/ar01s09.html
-
-// This was written by loosely following the top comment in NUT's parseconf.c.
-
-enum nut_parser_state
-{
- NUT_STATE_START_LINE, ///< Start of a line
- NUT_STATE_BETWEEN, ///< Between words, expecting non-WS
- NUT_STATE_UNQUOTED, ///< Within unquoted word
- NUT_STATE_UNQUOTED_ESCAPE, ///< Dtto after a backslash
- NUT_STATE_QUOTED, ///< Within a quoted word
- NUT_STATE_QUOTED_ESCAPE, ///< Dtto after a backslash
- NUT_STATE_QUOTED_END ///< End of word, expecting WS
-};
-
-struct nut_parser
-{
- enum nut_parser_state state; ///< Parser state
- struct str current_field; ///< Current field
-
- // Public:
-
- struct str_vector fields; ///< Line fields
-};
-
-static void
-nut_parser_init (struct nut_parser *self)
-{
- self->state = NUT_STATE_START_LINE;
- str_init (&self->current_field);
- str_vector_init (&self->fields);
-}
-
-static void
-nut_parser_free (struct nut_parser *self)
-{
- str_free (&self->current_field);
- str_vector_free (&self->fields);
-}
-
-static int
-nut_parser_end_field (struct nut_parser *self, char c)
-{
- str_vector_add (&self->fields, self->current_field.str);
- str_reset (&self->current_field);
-
- if (c == '\n')
- {
- self->state = NUT_STATE_START_LINE;
- return 1;
- }
-
- self->state = NUT_STATE_BETWEEN;
- return 0;
-}
-
-/// Returns 1 if a complete line has been read, -1 on error, 0 otherwise
-static int
-nut_parser_push (struct nut_parser *self, char c)
-{
- switch (self->state)
- {
- case NUT_STATE_START_LINE:
- str_vector_reset (&self->fields);
- str_reset (&self->current_field);
- self->state = NUT_STATE_BETWEEN;
- // Fall-through
-
- case NUT_STATE_BETWEEN:
- if (c == '\\')
- self->state = NUT_STATE_UNQUOTED_ESCAPE;
- else if (c == '"')
- self->state = NUT_STATE_QUOTED;
- else if (c == '\n' && self->fields.len)
- {
- self->state = NUT_STATE_START_LINE;
- return 1;
- }
- else if (!isspace_ascii (c))
- {
- str_append_c (&self->current_field, c);
- self->state = NUT_STATE_UNQUOTED;
- }
- return 0;
-
- case NUT_STATE_UNQUOTED:
- if (c == '\\')
- self->state = NUT_STATE_UNQUOTED_ESCAPE;
- else if (c == '"')
- return -1;
- else if (!isspace_ascii (c))
- str_append_c (&self->current_field, c);
- else
- return nut_parser_end_field (self, c);
- return 0;
-
- case NUT_STATE_UNQUOTED_ESCAPE:
- str_append_c (&self->current_field, c);
- self->state = NUT_STATE_UNQUOTED;
- return 0;
-
- case NUT_STATE_QUOTED:
- if (c == '\\')
- self->state = NUT_STATE_QUOTED_ESCAPE;
- else if (c == '"')
- self->state = NUT_STATE_QUOTED_END;
- else
- str_append_c (&self->current_field, c);
- return 0;
-
- case NUT_STATE_QUOTED_ESCAPE:
- str_append_c (&self->current_field, c);
- self->state = NUT_STATE_QUOTED;
- return 0;
-
- case NUT_STATE_QUOTED_END:
- if (!isspace_ascii (c))
- return -1;
- return nut_parser_end_field (self, c);
- }
-
- // Silence the compiler
- hard_assert (!"unhandled NUT parser state");
- return -1;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-struct nut_line
-{
- LIST_HEADER (struct nut_line)
-
- struct str_vector fields; ///< Parsed fields from the line
-};
-
-struct nut_response
-{
- struct nut_line *data; ///< Raw result data
-
- bool success; ///< Whether a general failure occured
- char *message; ///< Eventually an error ID string
-};
-
-/// Task completion callback
-typedef void (*nut_client_task_cb)
- (const struct nut_response *response, void *user_data);
-
-struct nut_client_task
-{
- LIST_HEADER (struct nut_client_task)
-
- nut_client_task_cb callback; ///< Callback on completion
- void *user_data; ///< User data
-};
-
-enum nut_client_state
-{
- NUT_DISCONNECTED, ///< Not connected
- NUT_CONNECTING, ///< Currently connecting
- NUT_CONNECTED ///< Connected
-};
-
-struct nut_client
-{
- struct poller *poller; ///< Poller
-
- // Connection:
-
- enum nut_client_state state; ///< Connection state
- struct connector *connector; ///< Connection establisher
-
- int socket; ///< MPD socket
- struct str read_buffer; ///< Input yet to be processed
- struct str write_buffer; ///< Outut yet to be be sent out
- struct poller_fd socket_event; ///< We can read from the socket
-
- // Protocol:
-
- struct nut_parser parser; ///< Protocol parser
- struct nut_line *data; ///< Data from last command
- struct nut_line *data_tail; ///< Tail of data list
- bool in_list; ///< Currently within a list
-
- struct nut_client_task *tasks; ///< Task queue
- struct nut_client_task *tasks_tail; ///< Tail of task queue
-
- // User configuration:
-
- void *user_data; ///< User data for callbacks
-
- /// Callback after connection has been successfully established
- void (*on_connected) (void *user_data);
-
- /// Callback for general failures or even normal disconnection;
- /// the interface is reinitialized
- void (*on_failure) (void *user_data);
-};
-
-static void nut_client_reset (struct nut_client *self);
-static void nut_client_destroy_connector (struct nut_client *self);
-
-static void
-nut_client_init (struct nut_client *self, struct poller *poller)
-{
- memset (self, 0, sizeof *self);
-
- self->poller = poller;
- self->socket = -1;
-
- str_init (&self->read_buffer);
- str_init (&self->write_buffer);
-
- nut_parser_init (&self->parser);
-
- poller_fd_init (&self->socket_event, poller, -1);
-}
-
-static void
-nut_client_free (struct nut_client *self)
-{
- // So that we don't have to repeat most of the stuff
- nut_client_reset (self);
-
- str_free (&self->read_buffer);
- str_free (&self->write_buffer);
-
- nut_parser_free (&self->parser);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-nut_client_flush_data (struct nut_client *self)
-{
- LIST_FOR_EACH (struct nut_line, iter, self->data)
- {
- str_vector_free (&iter->fields);
- free (iter);
- }
- self->data = self->data_tail = NULL;
-}
-
-/// Reinitialize the interface so that you can reconnect anew
-static void
-nut_client_reset (struct nut_client *self)
-{
- if (self->state == NUT_CONNECTING)
- nut_client_destroy_connector (self);
-
- if (self->socket != -1)
- xclose (self->socket);
- self->socket = -1;
-
- self->socket_event.closed = true;
- poller_fd_reset (&self->socket_event);
-
- str_reset (&self->read_buffer);
- str_reset (&self->write_buffer);
-
- self->parser.state = NUT_STATE_START_LINE;
- nut_client_flush_data (self);
- self->in_list = false;
-
- LIST_FOR_EACH (struct nut_client_task, iter, self->tasks)
- free (iter);
- self->tasks = self->tasks_tail = NULL;
-
- self->state = NUT_DISCONNECTED;
-}
-
-static void
-nut_client_fail (struct nut_client *self)
-{
- nut_client_reset (self);
- if (self->on_failure)
- self->on_failure (self->user_data);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-nut_client_quote (const char *s, struct str *output)
-{
- str_append_c (output, '"');
- for (; *s; s++)
- {
- if (*s == '"' || *s == '\\')
- str_append_c (output, '\\');
- str_append_c (output, *s);
- }
- str_append_c (output, '"');
-}
-
-static bool
-nut_client_must_quote (const char *s)
-{
- if (!*s)
- return true;
- for (; *s; s++)
- if ((unsigned char) *s <= ' ' || *s == '"' || *s == '\\')
- return true;
- return false;
-}
-
-static void
-nut_client_serialize (char **commands, struct str *line)
-{
- for (; *commands; commands++)
- {
- if (line->len)
- str_append_c (line, ' ');
-
- if (nut_client_must_quote (*commands))
- nut_client_quote (*commands, line);
- else
- str_append (line, *commands);
- }
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-nut_client_dispatch (struct nut_client *self, struct nut_response *response)
-{
- struct nut_client_task *task;
- if (!(task = self->tasks))
- return;
-
- if (task->callback)
- task->callback (response, task->user_data);
- nut_client_flush_data (self);
-
- LIST_UNLINK_WITH_TAIL (self->tasks, self->tasks_tail, task);
- free (task);
-}
-
-static bool
-nut_client_parse_line (struct nut_client *self)
-{
- struct str reconstructed;
- str_init (&reconstructed);
- nut_client_serialize (self->parser.fields.vector, &reconstructed);
- print_debug ("NUT >> %s", reconstructed.str);
- str_free (&reconstructed);
-
- struct str_vector *fields = &self->parser.fields;
- hard_assert (fields->len != 0);
-
- // Lists are always dispatched as their innards (and they can be empty)
- if (fields->len >= 2
- && !strcmp (fields->vector[0], "BEGIN")
- && !strcmp (fields->vector[1], "LIST"))
- self->in_list = true;
- else if (fields->len >= 2
- && !strcmp (fields->vector[0], "END")
- && !strcmp (fields->vector[1], "LIST"))
- self->in_list = false;
- else
- {
- struct nut_line *line = xcalloc (1, sizeof *line);
- str_vector_init (&line->fields);
- str_vector_add_vector (&line->fields, fields->vector);
- LIST_APPEND_WITH_TAIL (self->data, self->data_tail, line);
- }
-
- if (!self->in_list)
- {
- struct nut_response response;
- memset (&response, 0, sizeof response);
- response.success = true;
- response.data = self->data;
-
- if (!strcmp (fields->vector[0], "ERR"))
- {
- response.success = false;
- if (fields->len < 2)
- return false;
- response.message = xstrdup (fields->vector[1]);
- }
-
- nut_client_dispatch (self, &response);
- free (response.message);
- }
- return true;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-nut_client_update_poller (struct nut_client *self)
-{
- poller_fd_set (&self->socket_event,
- self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN);
-}
-
-static bool
-nut_client_process_input (struct nut_client *self)
-{
- struct str *rb = &self->read_buffer;
- for (size_t i = 0; i < rb->len; i++)
- {
- int res = nut_parser_push (&self->parser, rb->str[i]);
- if (res == -1 || (res == 1 && !nut_client_parse_line (self)))
- return false;
- }
-
- str_reset (rb);
- return true;
-}
-
-static void
-nut_client_on_ready (const struct pollfd *pfd, void *user_data)
-{
- (void) pfd;
-
- struct nut_client *self = user_data;
- bool read_succeeded = socket_io_try_read
- (self->socket, &self->read_buffer) == SOCKET_IO_OK;
-
- // Whether or not the read was successful, we need to process all data
- if (!nut_client_process_input (self) || !read_succeeded
- || socket_io_try_write (self->socket, &self->write_buffer) != SOCKET_IO_OK)
- nut_client_fail (self);
- else
- nut_client_update_poller (self);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-/// Beware that delivery of the event isn't deferred and you musn't make
-/// changes to the interface while processing the event!
-static void
-nut_client_add_task
- (struct nut_client *self, nut_client_task_cb cb, void *user_data)
-{
- struct nut_client_task *task = xcalloc (1, sizeof *self);
- task->callback = cb;
- task->user_data = user_data;
- LIST_APPEND_WITH_TAIL (self->tasks, self->tasks_tail, task);
-}
-
-/// Send a command. Remember to call nut_client_add_task() to handle responses,
-/// unless the command generates none.
-static void nut_client_send_command
- (struct nut_client *self, const char *command, ...) ATTRIBUTE_SENTINEL;
-
-static void
-nut_client_send_commandv (struct nut_client *self, char **commands)
-{
- struct str line;
- str_init (&line);
- nut_client_serialize (commands, &line);
-
- print_debug ("NUT << %s", line.str);
- str_append_c (&line, '\n');
- str_append_str (&self->write_buffer, &line);
- str_free (&line);
-
- nut_client_update_poller (self);
-}
-
-static void
-nut_client_send_command (struct nut_client *self, const char *command, ...)
-{
- struct str_vector v;
- str_vector_init (&v);
-
- va_list ap;
- va_start (ap, command);
- for (; command; command = va_arg (ap, const char *))
- str_vector_add (&v, command);
- va_end (ap);
-
- nut_client_send_commandv (self, v.vector);
- str_vector_free (&v);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-nut_client_finish_connection (struct nut_client *self, int socket)
-{
- set_blocking (socket, false);
- self->socket = socket;
- self->state = NUT_CONNECTED;
-
- poller_fd_init (&self->socket_event, self->poller, self->socket);
- self->socket_event.dispatcher = nut_client_on_ready;
- self->socket_event.user_data = self;
-
- nut_client_update_poller (self);
-
- if (self->on_connected)
- self->on_connected (self->user_data);
-}
-
-static void
-nut_client_destroy_connector (struct nut_client *self)
-{
- if (self->connector)
- connector_free (self->connector);
- free (self->connector);
- self->connector = NULL;
-
- // Not connecting anymore
- self->state = NUT_DISCONNECTED;
-}
-
-static void
-nut_client_on_connector_failure (void *user_data)
-{
- struct nut_client *self = user_data;
- nut_client_destroy_connector (self);
- nut_client_fail (self);
-}
-
-static void
-nut_client_on_connector_connected
- (void *user_data, int socket, const char *host)
-{
- (void) host;
-
- struct nut_client *self = user_data;
- nut_client_destroy_connector (self);
- nut_client_finish_connection (self, socket);
-}
-
-static void
-nut_client_connect
- (struct nut_client *self, const char *address, const char *service)
-{
- hard_assert (self->state == NUT_DISCONNECTED);
-
- struct connector *connector = xmalloc (sizeof *connector);
- connector_init (connector, self->poller);
- self->connector = connector;
-
- connector->user_data = self;
- connector->on_connected = nut_client_on_connector_connected;
- connector->on_failure = nut_client_on_connector_failure;
-
- connector_add_target (connector, address, service);
- self->state = NUT_CONNECTING;
-}
-
-// --- Configuration -----------------------------------------------------------
-
-static struct simple_config_item g_config_table[] =
-{
- { "mpd_address", "localhost", "MPD host or socket" },
- { "mpd_service", "6600", "MPD service name or port" },
- { "mpd_password", NULL, "MPD password" },
-
- { "nut_enabled", "off", "NUT UPS status reading enabled" },
- { "nut_load_thld", "50", "NUT threshold for load display" },
-
- // This is just a hack because my UPS doesn't report that value; a more
- // proper way of providing this information would be by making use of the
- // enhanced configuration format and allowing arbitrary per-UPS overrides
- { "nut_load_power", NULL, "ups.realpower.nominal override" },
-
- { NULL, NULL, NULL }
-};
-
-// --- Application -------------------------------------------------------------
-
-struct app_context
-{
- struct str_map config; ///< Program configuration
-
- Display *dpy; ///< X display handle
- int xkb_base_event_code; ///< Xkb base event code
- const char *prefix; ///< User-defined prefix
-
- struct poller poller; ///< Poller
- struct poller_timer time_changed; ///< Time change timer
- struct poller_timer make_context; ///< Start PulseAudio communication
- struct poller_timer refresh_rest; ///< Refresh unpollable information
-
- // Hotkeys:
-
- struct poller_fd x_event; ///< X11 event
- char *layout; ///< Keyboard layout
-
- // MPD:
-
- struct poller_timer mpd_reconnect; ///< Start MPD communication
- struct mpd_client mpd_client; ///< MPD client
-
- char *mpd_song; ///< MPD current song
- char *mpd_status; ///< MPD status (overrides song)
-
- // NUT:
-
- struct poller_timer nut_reconnect; ///< Start NUT communication
- struct nut_client nut_client; ///< NUT client
- struct str_map nut_ups_info; ///< Per-UPS information
- bool nut_success; ///< Information retrieved successfully
-
- char *nut_status; ///< NUT status
-
- // PulseAudio:
-
- pa_mainloop_api *api; ///< PulseAudio event loop proxy
- pa_context *context; ///< PulseAudio connection context
-
- bool failed; ///< General PulseAudio failure
-
- pa_cvolume sink_volume; ///< Current volume
- bool sink_muted; ///< Currently muted?
- struct str_vector sink_ports; ///< All sink port names
- char *sink_port_active; ///< Active sink port
-
- bool source_muted; ///< Currently muted?
-};
-
-static void
-str_map_destroy (void *self)
-{
- str_map_free (self);
- free (self);
-}
-
-static void
-app_context_init (struct app_context *self)
-{
- memset (self, 0, sizeof *self);
-
- str_map_init (&self->config);
- self->config.free = free;
- simple_config_load_defaults (&self->config, g_config_table);
-
- if (!(self->dpy = XkbOpenDisplay
- (NULL, &self->xkb_base_event_code, NULL, NULL, NULL, NULL)))
- exit_fatal ("cannot open display");
-
- poller_init (&self->poller);
- self->api = poller_pa_new (&self->poller);
-
- poller_fd_init (&self->x_event, &self->poller,
- ConnectionNumber (self->dpy));
-
- mpd_client_init (&self->mpd_client, &self->poller);
-
- nut_client_init (&self->nut_client, &self->poller);
- str_map_init (&self->nut_ups_info);
- self->nut_ups_info.free = str_map_destroy;
-
- str_vector_init (&self->sink_ports);
-}
-
-static void
-app_context_free (struct app_context *self)
-{
- str_map_free (&self->config);
-
- poller_fd_reset (&self->x_event);
- free (self->layout);
-
- if (self->context) pa_context_unref (self->context);
- if (self->dpy) XCloseDisplay (self->dpy);
-
- poller_pa_destroy (self->api);
- poller_free (&self->poller);
-
- mpd_client_free (&self->mpd_client);
- free (self->mpd_song);
- free (self->mpd_status);
-
- nut_client_free (&self->nut_client);
- str_map_free (&self->nut_ups_info);
-
- str_vector_free (&self->sink_ports);
- free (self->sink_port_active);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static char *
-read_value (int dir, const char *filename, struct error **e)
-{
- int fd = openat (dir, filename, O_RDONLY);
- if (fd < 0)
- {
- error_set (e, "%s: %s: %s", filename, "openat", strerror (errno));
- return NULL;
- }
-
- FILE *fp = fdopen (fd, "r");
- if (!fp)
- {
- error_set (e, "%s: %s: %s", filename, "fdopen", strerror (errno));
- close (fd);
- return NULL;
- }
-
- struct str s;
- str_init (&s);
- bool success = read_line (fp, &s);
- fclose (fp);
-
- if (!success)
- {
- error_set (e, "%s: %s", filename, "read failed");
- return NULL;
- }
- return str_steal (&s);
-}
-
-static unsigned long
-read_number (int dir, const char *filename, struct error **e)
-{
- char *value;
- if (!(value = read_value (dir, filename, e)))
- return false;
-
- unsigned long number = 0;
- if (!xstrtoul (&number, value, 10))
- error_set (e, "%s: %s", filename, "doesn't contain an unsigned number");
- free (value);
- return number;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static char *
-read_battery_status (int dir, struct error **e)
-{
- char *result = NULL;
- struct error *error = NULL;
-
- char *status;
- double charge_now;
- double charge_full;
-
- if ((status = read_value (dir, "status", &error), error)
- || (charge_now = read_number (dir, "charge_now", &error), error)
- || (charge_full = read_number (dir, "charge_full", &error), error))
- error_propagate (e, error);
- else
- result = xstrdup_printf ("%s (%u%%)",
- status, (unsigned) (charge_now / charge_full * 100 + 0.5));
-
- free (status);
- return result;
-}
-
-static char *
-try_power_supply (int dir, struct error **e)
-{
- char *type;
- struct error *error = NULL;
- if (!(type = read_value (dir, "type", &error)))
- {
- error_propagate (e, error);
- return NULL;
- }
-
- bool is_relevant =
- !strcmp (type, "Battery") ||
- !strcmp (type, "UPS");
-
- char *result = NULL;
- if (is_relevant)
- {
- char *status = read_battery_status (dir, &error);
- if (error)
- error_propagate (e, error);
- if (status)
- result = xstrdup_printf ("%s %s", type, status);
- free (status);
- }
- free (type);
- return result;
-}
-
-static char *
-make_battery_status (void)
-{
- DIR *power_supply = opendir ("/sys/class/power_supply");
- if (!power_supply)
- {
- print_debug ("cannot access %s: %s: %s",
- "/sys/class/power_supply", "opendir", strerror (errno));
- return NULL;
- }
-
- struct dirent *entry;
- char *status = NULL;
- while (!status && (entry = readdir (power_supply)))
- {
- const char *device_name = entry->d_name;
- if (device_name[0] == '.')
- continue;
-
- int dir = openat (dirfd (power_supply), device_name, O_RDONLY);
- if (dir < 0)
- {
- print_error ("%s: %s: %s", device_name, "openat", strerror (errno));
- continue;
- }
-
- struct error *error = NULL;
- status = try_power_supply (dir, &error);
- close (dir);
- if (error)
- {
- print_error ("%s: %s", device_name, error->message);
- error_free (error);
- }
- }
- closedir (power_supply);
- return status;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static char *
-make_time_status (char *fmt)
-{
- char buf[129] = "";
- time_t now = time (NULL);
- struct tm *local = localtime (&now);
-
- if (local == NULL)
- exit_fatal ("%s: %s", "localtime", strerror (errno));
- if (!strftime (buf, sizeof buf, fmt, local))
- exit_fatal ("strftime == 0");
-
- return xstrdup (buf);
-}
-
-#define VOLUME_PERCENT(x) (((x) * 100 + PA_VOLUME_NORM / 2) / PA_VOLUME_NORM)
-
-static char *
-make_volume_status (struct app_context *ctx)
-{
- if (!ctx->sink_volume.channels)
- return xstrdup ("");
-
- struct str s;
- str_init (&s);
- str_append_printf (&s, "%u%%", VOLUME_PERCENT (ctx->sink_volume.values[0]));
- if (!pa_cvolume_channels_equal_to (&ctx->sink_volume, ctx->sink_volume.values[0]))
- for (size_t i = 1; i < ctx->sink_volume.channels; i++)
- str_append_printf (&s, " / %u%%",
- VOLUME_PERCENT (ctx->sink_volume.values[i]));
- return str_steal (&s);
-}
-
-static void
-refresh_status (struct app_context *ctx)
-{
- struct str status;
- str_init (&status);
-
- if (ctx->prefix)
- str_append_printf (&status, "%s ", ctx->prefix);
-
- if (ctx->mpd_status)
- str_append_printf (&status, "%s ", ctx->mpd_status);
- else if (ctx->mpd_song)
- str_append_printf (&status, "%s ", ctx->mpd_song);
-
- if (ctx->failed)
- str_append_printf (&status, "%s ", "PA failure");
- else
- {
- char *volumes = make_volume_status (ctx);
- str_append_printf (&status, "%s%s ",
- ctx->sink_muted ? "Muted " : "", volumes);
- free (volumes);
- }
-
- char *battery = make_battery_status ();
- if (battery)
- str_append_printf (&status, "%s ", battery);
- free (battery);
-
- if (ctx->nut_status)
- str_append_printf (&status, "%s ", ctx->nut_status);
-
- if (ctx->layout)
- str_append_printf (&status, "%s ", ctx->layout);
-
- char *times = make_time_status ("Week %V, %a %d %b %Y %H:%M %Z");
- str_append (&status, times);
- free (times);
-
- set_dwm_status (ctx->dpy, status.str);
- str_free (&status);
-}
-
-static void
-on_time_changed (void *user_data)
-{
- struct app_context *ctx = user_data;
- refresh_status (ctx);
-
- const time_t now = time (NULL);
- const time_t next = (now / 60 + 1) * 60;
- poller_timer_set (&ctx->time_changed, (next - now) * 1000);
-}
-
-static void
-on_refresh_rest (void *user_data)
-{
- struct app_context *ctx = user_data;
-
- // We cannot use poll() on most sysfs entries, including battery charge
-
- refresh_status (ctx);
- poller_timer_set (&ctx->refresh_rest, 5000);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-// Sometimes it's not that easy and there can be repeating entries
-static void
-mpd_vector_to_map (const struct str_vector *data, struct str_map *map)
-{
- str_map_init (map);
- map->key_xfrm = tolower_ascii_strxfrm;
- map->free = free;
-
- char *key, *value;
- for (size_t i = 0; i < data->len; i++)
- {
- if ((key = mpd_client_parse_kv (data->vector[i], &value)))
- str_map_set (map, key, xstrdup (value));
- else
- print_debug ("%s: %s", "erroneous MPD output", data->vector[i]);
- }
-}
-
-static void
-mpd_on_info_response (const struct mpd_response *response,
- const struct str_vector *data, void *user_data)
-{
- if (!response->success)
- {
- print_debug ("%s: %s",
- "retrieving MPD info failed", response->message_text);
- return;
- }
-
- struct app_context *ctx = user_data;
- struct str_map map;
- mpd_vector_to_map (data, &map);
-
- free (ctx->mpd_status);
- ctx->mpd_status = NULL;
-
- struct str s;
- str_init (&s);
-
- const char *value;
- if ((value = str_map_find (&map, "state")))
- {
- if (!strcmp (value, "stop"))
- ctx->mpd_status = xstrdup ("MPD stopped");
- else if (!strcmp (value, "pause"))
- str_append (&s, "|| ");
- else
- str_append (&s, "|> ");
- }
-
- if ((value = str_map_find (&map, "title"))
- || (value = str_map_find (&map, "name"))
- || (value = str_map_find (&map, "file")))
- str_append_printf (&s, "\"%s\"", value);
- if ((value = str_map_find (&map, "artist")))
- str_append_printf (&s, " by \"%s\"", value);
- if ((value = str_map_find (&map, "album")))
- str_append_printf (&s, " from \"%s\"", value);
-
- free (ctx->mpd_song);
- ctx->mpd_song = str_steal (&s);
-
- refresh_status (ctx);
- str_map_free (&map);
-}
-
-static void
-mpd_request_info (struct app_context *ctx)
-{
- struct mpd_client *c = &ctx->mpd_client;
-
- mpd_client_list_begin (c);
- mpd_client_send_command (c, "currentsong", NULL);
- mpd_client_send_command (c, "status", NULL);
- mpd_client_list_end (c);
- mpd_client_add_task (c, mpd_on_info_response, ctx);
-
- mpd_client_idle (c, 0);
-}
-
-static void
-mpd_on_events (unsigned subsystems, void *user_data)
-{
- struct app_context *ctx = user_data;
- struct mpd_client *c = &ctx->mpd_client;
-
- if (subsystems & (MPD_SUBSYSTEM_PLAYER | MPD_SUBSYSTEM_PLAYLIST))
- mpd_request_info (ctx);
- else
- mpd_client_idle (c, 0);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-mpd_queue_reconnect (struct app_context *ctx)
-{
- poller_timer_set (&ctx->mpd_reconnect, 30 * 1000);
-}
-
-static void
-mpd_on_password_response (const struct mpd_response *response,
- const struct str_vector *data, void *user_data)
-{
- (void) data;
- struct app_context *ctx = user_data;
- struct mpd_client *c = &ctx->mpd_client;
-
- if (response->success)
- mpd_request_info (ctx);
- else
- {
- print_error ("%s: %s",
- "couldn't authenticate to MPD", response->message_text);
- mpd_client_send_command (c, "close", NULL);
- }
-}
-
-static void
-mpd_on_connected (void *user_data)
-{
- struct app_context *ctx = user_data;
- struct mpd_client *c = &ctx->mpd_client;
-
- const char *password = str_map_find (&ctx->config, "mpd_password");
- if (password)
- {
- mpd_client_send_command (c, "password", password, NULL);
- mpd_client_add_task (c, mpd_on_password_response, ctx);
- }
- else
- mpd_request_info (ctx);
-}
-
-static void
-mpd_on_failure (void *user_data)
-{
- // This is also triggered both by a failed connect and a clean disconnect
- struct app_context *ctx = user_data;
- print_error ("connection to MPD failed");
- mpd_queue_reconnect (ctx);
-}
-
-static void
-mpd_on_io_hook (void *user_data, bool outgoing, const char *line)
-{
- (void) user_data;
- if (outgoing)
- print_debug ("MPD << %s", line);
- else
- print_debug ("MPD >> %s", line);
-}
-
-static void
-on_mpd_reconnect (void *user_data)
-{
- struct app_context *ctx = user_data;
-
- struct mpd_client *c = &ctx->mpd_client;
- c->user_data = ctx;
- c->on_failure = mpd_on_failure;
- c->on_connected = mpd_on_connected;
- c->on_event = mpd_on_events;
- c->on_io_hook = mpd_on_io_hook;
-
- struct error *e = NULL;
- if (!mpd_client_connect (&ctx->mpd_client,
- str_map_find (&ctx->config, "mpd_address"),
- str_map_find (&ctx->config, "mpd_service"), &e))
- {
- print_error ("%s: %s", "cannot connect to MPD", e->message);
- error_free (e);
- mpd_queue_reconnect (ctx);
- }
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-nut_common_handler (const struct nut_response *response)
-{
- if (response->success)
- return true;
-
- print_error ("%s: %s", "retrieving NUT info failed", response->message);
- return false;
-}
-
-static void
-nut_translate_status (const char *status, struct str_vector *out)
-{
- // https://github.com/networkupstools/nut/blob/master/clients/status.h
- if (!strcmp (status, "OL")) str_vector_add (out, "on-line");
- if (!strcmp (status, "OB")) str_vector_add (out, "on battery");
- if (!strcmp (status, "LB")) str_vector_add (out, "low battery");
- if (!strcmp (status, "RB")) str_vector_add (out, "replace battery");
- if (!strcmp (status, "CHRG")) str_vector_add (out, "charging");
- if (!strcmp (status, "DISCHRG")) str_vector_add (out, "discharging");
- if (!strcmp (status, "OVER")) str_vector_add (out, "overload");
- if (!strcmp (status, "OFF")) str_vector_add (out, "off");
- if (!strcmp (status, "TRIM")) str_vector_add (out, "voltage trim");
- if (!strcmp (status, "BOOST")) str_vector_add (out, "voltage boost");
- if (!strcmp (status, "BYPASS")) str_vector_add (out, "bypass");
-}
-
-static char *
-interval_string (unsigned long seconds)
-{
- unsigned long hours = seconds / 3600; seconds %= 3600;
- unsigned long mins = seconds / 60; seconds %= 60;
- return xstrdup_printf ("%lu:%02lu:%02lu", hours, mins, seconds);
-}
-
-static void
-nut_process_ups (struct app_context *ctx, struct str_vector *ups_list,
- const char *ups_name, struct str_map *dict)
-{
- // Not currently interested in this kind of information;
- // maybe if someone had more than one UPS installed
- (void) ups_name;
-
- // http://www.networkupstools.org/docs/developer-guide.chunked/apas01.html
- const char *status = str_map_find (dict, "ups.status");
- const char *charge = str_map_find (dict, "battery.charge");
- const char *runtime = str_map_find (dict, "battery.runtime");
- const char *load = str_map_find (dict, "ups.load");
-
- if (!soft_assert (status && charge && runtime))
- return;
-
- unsigned long runtime_sec;
- if (!soft_assert (xstrtoul (&runtime_sec, runtime, 10)))
- return;
-
- struct str_vector items;
- str_vector_init (&items);
-
- bool running_on_batteries = false;
-
- struct str_vector v;
- str_vector_init (&v);
- cstr_split (status, " ", true, &v);
- for (size_t i = 0; i < v.len; i++)
- {
- const char *status = v.vector[i];
- nut_translate_status (status, &items);
-
- if (!strcmp (status, "OB"))
- running_on_batteries = true;
- }
- str_vector_free (&v);
-
- if (running_on_batteries || strcmp (charge, "100"))
- str_vector_add_owned (&items, xstrdup_printf ("%s%%", charge));
- if (running_on_batteries)
- str_vector_add_owned (&items, interval_string (runtime_sec));
-
- // Only show load if it's higher than the threshold so as to not distract
- const char *threshold = str_map_find (&ctx->config, "nut_load_thld");
- unsigned long load_n, threshold_n;
- if (load
- && xstrtoul (&load_n, load, 10)
- && xstrtoul (&threshold_n, threshold, 10)
- && load_n >= threshold_n)
- {
- struct str item;
- str_init (&item);
- str_append_printf (&item, "load %s%%", load);
-
- const char *power = str_map_find (dict, "ups.realpower.nominal");
- // Override if NUT cannot tell it correctly for whatever reason
- if (!power) power = str_map_find (&ctx->config, "nut_load_power");
-
- // Approximation of how much electricity the perpihery actually uses
- unsigned long power_n;
- if (power && xstrtoul (&power_n, power, 10))
- str_append_printf (&item, " (~%luW)", power_n * load_n / 100);
-
- str_vector_add_owned (&items, str_steal (&item));
- }
-
- struct str result;
- str_init (&result);
- str_append (&result, "UPS: ");
-
- for (size_t i = 0; i < items.len; i++)
- {
- if (i) str_append (&result, "; ");
- str_append (&result, items.vector[i]);
- }
- str_vector_free (&items);
- str_vector_add_owned (ups_list, str_steal (&result));
-}
-
-static void
-nut_on_logout_response (const struct nut_response *response, void *user_data)
-{
- if (!nut_common_handler (response))
- return;
-
- struct app_context *ctx = user_data;
- struct str_vector ups_list;
- str_vector_init (&ups_list);
-
- struct str_map_iter iter;
- str_map_iter_init (&iter, &ctx->nut_ups_info);
- struct str_map *dict;
- while ((dict = str_map_iter_next (&iter)))
- nut_process_ups (ctx, &ups_list, iter.link->key, dict);
-
- free (ctx->nut_status);
- ctx->nut_status = NULL;
-
- if (ups_list.len)
- {
- struct str status;
- str_init (&status);
- str_append (&status, ups_list.vector[0]);
- for (size_t i = 1; i < ups_list.len; i++)
- str_append_printf (&status, " %s", ups_list.vector[0]);
- ctx->nut_status = str_steal (&status);
- }
-
- ctx->nut_success = true;
- str_vector_free (&ups_list);
- refresh_status (ctx);
-}
-
-static void
-nut_store_var (struct app_context *ctx,
- const char *ups_name, const char *key, const char *value)
-{
- struct str_map *map;
- if (!(map = str_map_find (&ctx->nut_ups_info, ups_name)))
- {
- str_map_init ((map = xcalloc (1, sizeof *map)));
- map->free = free;
- str_map_set (&ctx->nut_ups_info, ups_name, map);
- }
-
- str_map_set (map, key, xstrdup (value));
-}
-
-static void
-nut_on_var_response (const struct nut_response *response, void *user_data)
-{
- if (!nut_common_handler (response))
- return;
-
- struct app_context *ctx = user_data;
- LIST_FOR_EACH (struct nut_line, iter, response->data)
- {
- const struct str_vector *fields = &iter->fields;
- if (!soft_assert (fields->len >= 4
- && !strcmp (fields->vector[0], "VAR")))
- continue;
-
- nut_store_var (ctx, fields->vector[1],
- fields->vector[2], fields->vector[3]);
- }
-}
-
-static void
-nut_on_list_ups_response (const struct nut_response *response, void *user_data)
-{
- if (!nut_common_handler (response))
- return;
-
- struct app_context *ctx = user_data;
- struct nut_client *c = &ctx->nut_client;
-
- // Then we list all their properties and terminate the connection
- LIST_FOR_EACH (struct nut_line, iter, response->data)
- {
- const struct str_vector *fields = &iter->fields;
- if (!soft_assert (fields->len >= 2
- && !strcmp (fields->vector[0], "UPS")))
- continue;
-
- nut_client_send_command (c, "LIST", "VAR", fields->vector[1], NULL);
- nut_client_add_task (c, nut_on_var_response, ctx);
- }
-
- nut_client_send_command (c, "LOGOUT", NULL);
- nut_client_add_task (c, nut_on_logout_response, ctx);
-}
-
-static void
-nut_on_connected (void *user_data)
-{
- struct app_context *ctx = user_data;
- struct nut_client *c = &ctx->nut_client;
-
- // First we list all available UPS devices
- nut_client_send_command (c, "LIST", "UPS", NULL);
- nut_client_add_task (c, nut_on_list_ups_response, ctx);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-nut_indicate_failure (struct app_context *ctx)
-{
- free (ctx->nut_status);
- ctx->nut_status = xstrdup ("NUT failure");
-
- refresh_status (ctx);
-}
-
-static void
-nut_on_failure (void *user_data)
-{
- struct app_context *ctx = user_data;
-
- // This is also triggered both by a failed connect and a clean disconnect
- if (!ctx->nut_success)
- {
- print_error ("connection to NUT failed");
- nut_indicate_failure (ctx);
- }
-}
-
-static void
-on_nut_reconnect (void *user_data)
-{
- struct app_context *ctx = user_data;
-
- bool want_nut = false;
- if (!set_boolean_if_valid (&want_nut,
- str_map_find (&ctx->config, "nut_enabled")))
- print_error ("invalid configuration value for `%s'", "nut_enabled");
- if (!want_nut)
- return;
-
- struct nut_client *c = &ctx->nut_client;
- c->user_data = ctx;
- c->on_failure = nut_on_failure;
- c->on_connected = nut_on_connected;
-
- // So that we don't have to maintain a separate timeout timer,
- // we keep a simple periodic reconnect timer
- if (c->state != NUT_DISCONNECTED)
- {
- print_error ("failed to retrieve NUT status within the interval");
- nut_indicate_failure (ctx);
- nut_client_reset (c);
- }
-
- str_map_clear (&ctx->nut_ups_info);
-
- nut_client_connect (&ctx->nut_client, "localhost", "3493");
- ctx->nut_success = false;
- poller_timer_set (&ctx->nut_reconnect, 10 * 1000);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-#define DEFAULT_SOURCE "@DEFAULT_SOURCE@"
-#define DEFAULT_SINK "@DEFAULT_SINK@"
-
-static void
-on_sink_info (pa_context *context, const pa_sink_info *info, int eol,
- void *userdata)
-{
- (void) context;
-
- if (info && !eol)
- {
- struct app_context *ctx = userdata;
- ctx->sink_volume = info->volume;
- ctx->sink_muted = !!info->mute;
-
- str_vector_reset (&ctx->sink_ports);
- free (ctx->sink_port_active);
- ctx->sink_port_active = NULL;
-
- if (info->ports)
- for (struct pa_sink_port_info **iter = info->ports; *iter; iter++)
- str_vector_add (&ctx->sink_ports, (*iter)->name);
- if (info->active_port)
- ctx->sink_port_active = xstrdup (info->active_port->name);
-
- refresh_status (ctx);
- }
-}
-
-static void
-on_source_info (pa_context *context, const pa_source_info *info, int eol,
- void *userdata)
-{
- (void) context;
-
- if (info && !eol)
- {
- struct app_context *ctx = userdata;
- ctx->source_muted = !!info->mute;
- }
-}
-
-static void
-update_volume (struct app_context *ctx)
-{
- pa_operation_unref (pa_context_get_sink_info_by_name
- (ctx->context, DEFAULT_SINK, on_sink_info, ctx));
- pa_operation_unref (pa_context_get_source_info_by_name
- (ctx->context, DEFAULT_SOURCE, on_source_info, ctx));
-}
-
-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;
- if ((event & PA_SUBSCRIPTION_EVENT_TYPE_MASK)
- == PA_SUBSCRIPTION_EVENT_CHANGE)
- update_volume (ctx);
-}
-
-static void
-on_subscribe_finish (pa_context *context, int success, void *userdata)
-{
- (void) context;
-
- struct app_context *ctx = userdata;
- if (!success)
- {
- ctx->failed = true;
- refresh_status (ctx);
- }
-}
-
-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;
- refresh_status (ctx);
-
- pa_context_unref (context);
- ctx->context = NULL;
-
- // Retry after an arbitrary delay of 5 seconds
- poller_timer_set (&ctx->make_context, 5000);
- return;
- case PA_CONTEXT_READY:
- ctx->failed = false;
- refresh_status (ctx);
-
- pa_context_set_subscribe_callback (context, on_event, userdata);
- pa_operation_unref (pa_context_subscribe (context,
- PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE,
- on_subscribe_finish, userdata));
- update_volume (ctx);
- 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
-spawn (struct app_context *ctx, char *argv[])
-{
- posix_spawn_file_actions_t actions;
- posix_spawn_file_actions_init (&actions);
-
- posix_spawn_file_actions_addclose (&actions, ConnectionNumber (ctx->dpy));
- if (ctx->mpd_client.socket != -1)
- posix_spawn_file_actions_addclose (&actions, ctx->mpd_client.socket);
- if (ctx->nut_client.socket != -1)
- posix_spawn_file_actions_addclose (&actions, ctx->nut_client.socket);
-
- posix_spawnattr_t attr;
- posix_spawnattr_init (&attr);
- posix_spawnattr_setpgroup (&attr, 0);
-
- posix_spawnp (NULL, argv[0], &actions, &attr, argv, environ);
-
- posix_spawn_file_actions_destroy (&actions);
- posix_spawnattr_destroy (&attr);
-}
-
-#define MPD_SIMPLE(name, ...) \
- static void \
- on_mpd_ ## name (struct app_context *ctx, int arg) \
- { \
- (void) arg; \
- struct mpd_client *c = &ctx->mpd_client; \
- if (c->state != MPD_CONNECTED) \
- return; \
- mpd_client_send_command (c, __VA_ARGS__); \
- mpd_client_add_task (c, NULL, NULL); \
- mpd_client_idle (c, 0); \
- }
-
-// XXX: pause without argument is deprecated, we can watch play state
-// if we want to have the toggle pause/play functionality
-MPD_SIMPLE (play, "pause", NULL)
-MPD_SIMPLE (stop, "stop", NULL)
-MPD_SIMPLE (prev, "previous", NULL)
-MPD_SIMPLE (next, "next", NULL)
-MPD_SIMPLE (forward, "seekcur", "+10", NULL)
-MPD_SIMPLE (backward, "seekcur", "-10", NULL)
-
-static void
-on_volume_finish (pa_context *context, int success, void *userdata)
-{
- (void) context;
- (void) success;
- (void) userdata;
-
- // Just like... whatever, man
-}
-
-static void
-on_volume_mic_mute (struct app_context *ctx, int arg)
-{
- (void) arg;
-
- if (!ctx->context)
- return;
-
- pa_operation_unref (pa_context_set_source_mute_by_name (ctx->context,
- DEFAULT_SOURCE, !ctx->source_muted, on_volume_finish, ctx));
-}
-
-static void
-on_volume_switch (struct app_context *ctx, int arg)
-{
- (void) arg;
-
- if (!ctx->context || !ctx->sink_port_active || !ctx->sink_ports.len)
- return;
-
- size_t current = 0;
- for (size_t i = 0; i < ctx->sink_ports.len; i++)
- if (!strcmp (ctx->sink_port_active, ctx->sink_ports.vector[i]))
- current = i;
-
- pa_operation_unref (pa_context_set_sink_port_by_name (ctx->context,
- DEFAULT_SINK,
- ctx->sink_ports.vector[(current + 1) % ctx->sink_ports.len],
- on_volume_finish, ctx));
-}
-
-static void
-on_volume_mute (struct app_context *ctx, int arg)
-{
- (void) arg;
-
- if (!ctx->context)
- return;
-
- pa_operation_unref (pa_context_set_sink_mute_by_name (ctx->context,
- DEFAULT_SINK, !ctx->sink_muted, on_volume_finish, ctx));
-}
-
-static void
-on_volume_set (struct app_context *ctx, int arg)
-{
- if (!ctx->context)
- return;
-
- pa_cvolume volume = ctx->sink_volume;
- if (arg > 0)
- pa_cvolume_inc (&volume, (pa_volume_t) arg * PA_VOLUME_NORM / 100);
- else
- pa_cvolume_dec (&volume, (pa_volume_t) -arg * PA_VOLUME_NORM / 100);
- pa_operation_unref (pa_context_set_sink_volume_by_name (ctx->context,
- DEFAULT_SINK, &volume, on_volume_finish, ctx));
-}
-
-static void
-on_lock (struct app_context *ctx, int arg)
-{
- (void) arg;
-
- // One of these will work
- char *argv_gdm[] = { "gdm-switch-user", NULL };
- spawn (ctx, argv_gdm);
- char *argv_ldm[] = { "dm-tool", "lock", NULL };
- spawn (ctx, argv_ldm);
-}
-
-static void
-on_brightness (struct app_context *ctx, int arg)
-{
- char *value = xstrdup_printf ("%d", arg);
- char *argv[] = { "brightness", value, NULL };
- spawn (ctx, argv);
- free (value);
-}
-
-static void
-on_lock_group (struct app_context *ctx, int arg)
-{
- XkbLockGroup (ctx->dpy, XkbUseCoreKbd, arg);
-}
-
-struct
-{
- unsigned mod;
- KeySym keysym;
- void (*handler) (struct app_context *ctx, int arg);
- int arg;
-}
-g_keys[] =
-{
- // This key should be labeled L on normal Qwert[yz] layouts
- { Mod4Mask, XK_n, on_lock, 0 },
-
- // MPD
- { Mod4Mask, XK_Up, on_mpd_play, 0 },
- { Mod4Mask, XK_Down, on_mpd_stop, 0 },
- { Mod4Mask, XK_Left, on_mpd_prev, 0 },
- { Mod4Mask, XK_Right, on_mpd_next, 0 },
- /* xmodmap | grep -e Alt_R -e Meta_R -e ISO_Level3_Shift -e Mode_switch */
- { Mod4Mask | Mod5Mask, XK_Left, on_mpd_backward, 0 },
- { Mod4Mask | Mod5Mask, XK_Right, on_mpd_forward, 0 },
-
- // Keyboard groups
- { Mod4Mask, XK_F9, on_lock_group, 0 },
- { Mod4Mask, XK_F10, on_lock_group, 1 },
- { Mod4Mask, XK_F11, on_lock_group, 2 },
- { Mod4Mask, XK_F12, on_lock_group, 3 },
-
- // Brightness
- { Mod4Mask, XK_Home, on_brightness, 10 },
- { Mod4Mask, XK_End, on_brightness, -10 },
- { 0, XF86XK_MonBrightnessUp, on_brightness, 10 },
- { 0, XF86XK_MonBrightnessDown, on_brightness, -10 },
-
- // Volume
- { Mod4Mask, XK_Insert, on_volume_switch, 0 },
- { Mod4Mask, XK_Delete, on_volume_mute, 0 },
- { Mod4Mask, XK_Page_Up, on_volume_set, 5 },
- { Mod4Mask | Mod5Mask, XK_Page_Up, on_volume_set, 1 },
- { Mod4Mask, XK_Page_Down, on_volume_set, -5 },
- { Mod4Mask | Mod5Mask, XK_Page_Down, on_volume_set, -1 },
- { 0, XF86XK_AudioMicMute, on_volume_mic_mute, 0 },
- { 0, XF86XK_AudioMute, on_volume_mute, 0 },
- { 0, XF86XK_AudioRaiseVolume, on_volume_set, 5 },
- { 0, XF86XK_AudioLowerVolume, on_volume_set, -5 },
-};
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-on_x_keypress (struct app_context *ctx, XEvent *e)
-{
- XKeyEvent *ev = &e->xkey;
- unsigned unconsumed_mods;
- KeySym keysym;
- if (!XkbLookupKeySym (ctx->dpy,
- (KeyCode) ev->keycode, ev->state, &unconsumed_mods, &keysym))
- return;
- for (size_t i = 0; i < N_ELEMENTS (g_keys); i++)
- if (g_keys[i].keysym == keysym
- && g_keys[i].mod == ev->state
- && g_keys[i].handler)
- g_keys[i].handler (ctx, g_keys[i].arg);
-}
-
-static void
-on_xkb_event (struct app_context *ctx, XkbEvent *ev)
-{
- int group;
- if (ev->any.xkb_type == XkbStateNotify)
- group = ev->state.group;
- else
- {
- XkbStateRec rec;
- XkbGetState (ctx->dpy, XkbUseCoreKbd, &rec);
- group = rec.group;
- }
-
- XkbDescPtr desc = XkbAllocKeyboard ();
- XkbGetNames (ctx->dpy, XkbGroupNamesMask, desc);
-
- free (ctx->layout);
- ctx->layout = NULL;
-
- if (group != 0)
- {
- char *layout = XGetAtomName (ctx->dpy, desc->names->groups[group]);
- ctx->layout = xstrdup (layout);
- XFree (layout);
- }
-
- XkbFreeKeyboard (desc, 0, True);
- refresh_status (ctx);
-}
-
-static void
-on_x_ready (const struct pollfd *pfd, void *user_data)
-{
- (void) pfd;
- struct app_context *ctx = user_data;
-
- XkbEvent ev;
- while (XPending (ctx->dpy))
- {
- if (XNextEvent (ctx->dpy, &ev.core))
- exit_fatal ("XNextEvent returned non-zero");
- if (ev.type == KeyPress)
- on_x_keypress (ctx, &ev.core);
- if (ev.type == ctx->xkb_base_event_code)
- on_xkb_event (ctx, &ev);
- }
-}
-
-static void
-grab_keys (struct app_context *ctx)
-{
- unsigned ignored_locks =
- LockMask | XkbKeysymToModifiers (ctx->dpy, XK_Num_Lock);
- hard_assert (XkbSetIgnoreLockMods
- (ctx->dpy, XkbUseCoreKbd, ignored_locks, ignored_locks, 0, 0));
-
- KeyCode code;
- Window root = DefaultRootWindow (ctx->dpy);
- for (size_t i = 0; i < N_ELEMENTS (g_keys); i++)
- if ((code = XKeysymToKeycode (ctx->dpy, g_keys[i].keysym)))
- XGrabKey (ctx->dpy, code, g_keys[i].mod, root,
- False /* ? */, GrabModeAsync, GrabModeAsync);
-
- XSelectInput (ctx->dpy, root, KeyPressMask);
- XSync (ctx->dpy, False);
-
- ctx->x_event.dispatcher = on_x_ready;
- ctx->x_event.user_data = ctx;
- poller_fd_set (&ctx->x_event, POLLIN);
-
- // XXX: XkbMapNotify -> XkbRefreshKeyboardMapping(), ...?
- XkbSelectEventDetails (ctx->dpy, XkbUseCoreKbd, XkbNamesNotify,
- XkbAllNamesMask, XkbGroupNamesMask);
- XkbSelectEventDetails (ctx->dpy, XkbUseCoreKbd, XkbStateNotify,
- XkbAllStateComponentsMask, XkbGroupStateMask);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-poller_timer_init_and_set (struct poller_timer *self, struct poller *poller,
- poller_timer_fn cb, void *user_data)
-{
- poller_timer_init (self, poller);
- self->dispatcher = cb;
- self->user_data = user_data;
-
- poller_timer_set (self, 0);
-}
-
-int
-main (int argc, char *argv[])
-{
- g_log_message_real = log_message_custom;
-
- 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" },
- { 'w', "write-default-cfg", "FILENAME",
- OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
- "write a default configuration file and exit" },
- { 0, NULL, NULL, 0, NULL }
- };
-
- struct opt_handler oh;
- opt_handler_init (&oh, argc, argv, opts, NULL, "Set root window name.");
-
- 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);
- case 'w':
- call_simple_config_write_default (optarg, g_config_table);
- exit (EXIT_SUCCESS);
- default:
- print_error ("wrong options");
- opt_handler_usage (&oh, stderr);
- exit (EXIT_FAILURE);
- }
-
- argc -= optind;
- argv += optind;
-
- 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;
-
- struct error *e = NULL;
- if (!simple_config_update_from_file (&ctx.config, &e))
- exit_fatal ("%s", e->message);
-
- poller_timer_init_and_set (&ctx.time_changed, &ctx.poller,
- on_time_changed, &ctx);
- poller_timer_init_and_set (&ctx.make_context, &ctx.poller,
- on_make_context, &ctx);
- poller_timer_init_and_set (&ctx.refresh_rest, &ctx.poller,
- on_refresh_rest, &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,
- on_nut_reconnect, &ctx);
-
- grab_keys (&ctx);
-
- poller_pa_run (ctx.api);
- app_context_free (&ctx);
-
- return 0;
-}