Make the debug tab prettier

And the MPD code a little bit more generic.
This commit is contained in:
Přemysl Eric Janouch 2016-10-03 19:43:51 +02:00
parent 91b6a799c8
commit 1e39ae52c5
Signed by: p
GPG Key ID: B715679E3A361BE6
2 changed files with 146 additions and 52 deletions

11
mpd.c
View File

@ -1,4 +1,5 @@
// Copied from desktop-tools, should go to liberty if it proves useful // Copied from desktop-tools, should go to liberty if it proves useful
// XXX: changed: added a debugging callback instead of hardcoded print_debug
// --- MPD client interface ---------------------------------------------------- // --- MPD client interface ----------------------------------------------------
@ -107,6 +108,9 @@ struct mpd_client
/// Callback to receive "idle" updates. /// Callback to receive "idle" updates.
/// Remember to restart the idle if needed. /// Remember to restart the idle if needed.
void (*on_event) (unsigned subsystems, void *user_data); void (*on_event) (unsigned subsystems, void *user_data);
/// Callback to trace protocol I/O
void (*on_io_hook) (void *user_data, bool outgoing, const char *line);
}; };
static void mpd_client_reset (struct mpd_client *self); static void mpd_client_reset (struct mpd_client *self);
@ -258,7 +262,8 @@ mpd_client_parse_hello (struct mpd_client *self, const char *line)
static bool static bool
mpd_client_parse_line (struct mpd_client *self, const char *line) mpd_client_parse_line (struct mpd_client *self, const char *line)
{ {
print_debug ("MPD >> %s", line); if (self->on_io_hook)
self->on_io_hook (self->user_data, false, line);
if (!self->got_hello) if (!self->got_hello)
return mpd_client_parse_hello (self, line); return mpd_client_parse_hello (self, line);
@ -414,7 +419,9 @@ mpd_client_send_commandv (struct mpd_client *self, char **commands)
str_append (&line, *commands); str_append (&line, *commands);
} }
print_debug ("MPD << %s", line.str); if (self->on_io_hook)
self->on_io_hook (self->user_data, true, line.str);
str_append_c (&line, '\n'); str_append_c (&line, '\n');
str_append_str (&self->write_buffer, &line); str_append_str (&self->write_buffer, &line);
str_free (&line); str_free (&line);

187
nncmpp.c
View File

@ -20,10 +20,45 @@
#include "config.h" #include "config.h"
// We "need" to have an enum for attributes before including liberty.
// Avoiding colours in the defaults here in order to support dumb terminals.
#define ATTRIBUTE_TABLE(XX) \
XX( HEADER, "header", -1, -1, 0 ) \
XX( HIGHLIGHT, "highlight", -1, -1, A_BOLD ) \
/* Gauge */ \
XX( ELAPSED, "elapsed", -1, -1, A_REVERSE ) \
XX( REMAINS, "remains", -1, -1, A_UNDERLINE ) \
/* Tab bar */ \
XX( TAB_BAR, "tab_bar", -1, -1, A_REVERSE ) \
XX( TAB_ACTIVE, "tab_active", -1, -1, A_UNDERLINE ) \
/* Listview */ \
XX( EVEN, "even", -1, -1, 0 ) \
XX( ODD, "odd", -1, -1, 0 ) \
XX( SELECTION, "selection", -1, -1, A_REVERSE ) \
XX( SCROLLBAR, "scrollbar", -1, -1, 0 ) \
/* These are for debugging only */ \
XX( WARNING, "warning", 3, -1, 0 ) \
XX( ERROR, "error", 1, -1, 0 ) \
XX( INCOMING, "incoming", 2, -1, 0 ) \
XX( OUTGOING, "outgoing", 4, -1, 0 )
enum
{
#define XX(name, config, fg_, bg_, attrs_) ATTRIBUTE_ ## name,
ATTRIBUTE_TABLE (XX)
#undef XX
ATTRIBUTE_COUNT
};
// My battle-tested C framework acting as a GLib replacement. Its one big // My battle-tested C framework acting as a GLib replacement. Its one big
// disadvantage is missing support for i18n but that can eventually be added // disadvantage is missing support for i18n but that can eventually be added
// as an optional feature. Localised applications look super awkward, though. // as an optional feature. Localised applications look super awkward, though.
// User data for logger functions to enable formatted logging
#define print_fatal_data ((void *) ATTRIBUTE_ERROR)
#define print_error_data ((void *) ATTRIBUTE_ERROR)
#define print_warning_data ((void *) ATTRIBUTE_WARNING)
#define LIBERTY_WANT_POLLER #define LIBERTY_WANT_POLLER
#define LIBERTY_WANT_ASYNC #define LIBERTY_WANT_ASYNC
#include "liberty/liberty.c" #include "liberty/liberty.c"
@ -100,39 +135,6 @@ update_curses_terminal_size (void)
// Function names are prefixed mostly because of curses which clutters the // Function names are prefixed mostly because of curses which clutters the
// global namespace and makes it harder to distinguish what functions relate to. // global namespace and makes it harder to distinguish what functions relate to.
// Avoiding colours in the defaults here in order to support dumb terminals
#define ATTRIBUTE_TABLE(XX) \
XX( HEADER, "header", -1, -1, 0 ) \
XX( HIGHLIGHT, "highlight", -1, -1, A_BOLD ) \
\
XX( ELAPSED, "elapsed", -1, -1, A_REVERSE ) \
XX( REMAINS, "remains", -1, -1, A_UNDERLINE ) \
\
XX( TAB_BAR, "tab_bar", -1, -1, A_REVERSE ) \
XX( TAB_ACTIVE, "tab_active", -1, -1, A_UNDERLINE ) \
\
XX( EVEN, "even", -1, -1, 0 ) \
XX( ODD, "odd", -1, -1, 0 ) \
XX( SELECTION, "selection", -1, -1, A_REVERSE ) \
XX( SCROLLBAR, "scrollbar", -1, -1, 0 )
enum
{
#define XX(name, config, fg_, bg_, attrs_) ATTRIBUTE_ ## name,
ATTRIBUTE_TABLE (XX)
#undef XX
ATTRIBUTE_COUNT
};
struct attrs
{
short fg; ///< Foreground colour index
short bg; ///< Background colour index
chtype attrs; ///< Other attributes
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// The user interface is focused on conceptual simplicity. That is important // The user interface is focused on conceptual simplicity. That is important
// since we're not using any TUI framework (which are mostly a lost cause to me // since we're not using any TUI framework (which are mostly a lost cause to me
// in the post-Unicode era and not worth pursuing), and the code would get // in the post-Unicode era and not worth pursuing), and the code would get
@ -151,9 +153,8 @@ struct row_buffer;
typedef bool (*tab_event_fn) (struct tab *self, termo_key_t *event); typedef bool (*tab_event_fn) (struct tab *self, termo_key_t *event);
/// Draw an item to the screen using the row buffer API /// Draw an item to the screen using the row buffer API
// TODO: this will probably want to know the actual width typedef void (*tab_item_draw_fn) (struct tab *self,
typedef void (*tab_item_draw_fn) unsigned item_index, struct row_buffer *buffer, int width);
(struct tab *self, unsigned item_index, struct row_buffer *buffer);
struct tab struct tab
{ {
@ -181,6 +182,13 @@ struct tab
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct attrs
{
short fg; ///< Foreground colour index
short bg; ///< Background colour index
chtype attrs; ///< Other attributes
};
enum player_state { PLAYER_STOPPED, PLAYER_PLAYING, PLAYER_PAUSED }; enum player_state { PLAYER_STOPPED, PLAYER_PLAYING, PLAYER_PAUSED };
// Basically a container for most of the globals; no big sense in handing // Basically a container for most of the globals; no big sense in handing
@ -1005,7 +1013,13 @@ app_redraw_view (void)
struct row_buffer buf; struct row_buffer buf;
row_buffer_init (&buf); row_buffer_init (&buf);
tab->on_item_draw (tab, item_index, &buf); tab->on_item_draw (tab, item_index, &buf, view_width);
if (item_index == tab->item_selected)
{
// Make it so that the selection color always wins
LIST_FOR_EACH (struct row_char, iter, buf.chars)
iter->attrs &= ~(A_COLOR | A_REVERSE);
}
if (buf.total_width > view_width) if (buf.total_width > view_width)
row_buffer_ellipsis (&buf, view_width, row_attrs); row_buffer_ellipsis (&buf, view_width, row_attrs);
@ -1682,6 +1696,8 @@ mpd_on_failure (void *user_data)
mpd_queue_reconnect (); mpd_queue_reconnect ();
} }
static void mpd_on_io_hook (void *user_data, bool outgoing, const char *line);
static void static void
app_on_reconnect (void *user_data) app_on_reconnect (void *user_data)
{ {
@ -1691,6 +1707,7 @@ app_on_reconnect (void *user_data)
c->on_failure = mpd_on_failure; c->on_failure = mpd_on_failure;
c->on_connected = mpd_on_connected; c->on_connected = mpd_on_connected;
c->on_event = mpd_on_events; c->on_event = mpd_on_events;
c->on_io_hook = mpd_on_io_hook;
// We accept hostname/IPv4/IPv6 in pseudo-URL format, as well as sockets // We accept hostname/IPv4/IPv6 in pseudo-URL format, as well as sockets
char *address = xstrdup (get_config_string (g_ctx.config.root, char *address = xstrdup (get_config_string (g_ctx.config.root,
@ -1744,9 +1761,10 @@ g_help_items[] =
static void static void
help_tab_on_item_draw (struct tab *self, unsigned item_index, help_tab_on_item_draw (struct tab *self, unsigned item_index,
struct row_buffer *buffer) struct row_buffer *buffer, int width)
{ {
(void) self; (void) self;
(void) width;
hard_assert (item_index <= N_ELEMENTS (g_help_items)); hard_assert (item_index <= N_ELEMENTS (g_help_items));
row_buffer_append (buffer, g_help_items[item_index].text, 0); row_buffer_append (buffer, g_help_items[item_index].text, 0);
@ -1765,28 +1783,80 @@ help_tab_create (void)
// --- Debug tab --------------------------------------------------------------- // --- Debug tab ---------------------------------------------------------------
struct debug_item
{
char *text; ///< Logged line
int64_t timestamp; ///< Timestamp
chtype attrs; ///< Line attributes
};
static struct static struct
{ {
struct tab super; ///< Parent class struct tab super; ///< Parent class
struct str_vector lines; ///< Lines struct debug_item *items; ///< Items
size_t items_alloc; ///< How many items are allocated
bool active; ///< The tab is present bool active; ///< The tab is present
} }
g_debug_tab; g_debug_tab;
static void static void
debug_tab_on_item_draw (struct tab *self, unsigned item_index, debug_tab_on_item_draw (struct tab *self, unsigned item_index,
struct row_buffer *buffer) struct row_buffer *buffer, int width)
{ {
(void) self; (void) self;
hard_assert (item_index <= g_debug_tab.lines.len); hard_assert (item_index <= g_debug_tab.super.item_count);
row_buffer_append (buffer, g_debug_tab.lines.vector[item_index], 0); struct debug_item *item = &g_debug_tab.items[item_index];
char buf[16];
struct tm tm;
time_t when = item->timestamp / 1000;
strftime (buf, sizeof buf, "%T", localtime_r (&when, &tm));
char *prefix = xstrdup_printf
("%s.%03d", buf, (int) (item->timestamp % 1000));
row_buffer_append (buffer, prefix, 0);
free (prefix);
row_buffer_append (buffer, " ", item->attrs);
row_buffer_append (buffer, item->text, item->attrs);
// We override the formatting including colors -- do it for the whole line
if (buffer->total_width > width)
row_buffer_ellipsis (buffer, width, item->attrs);
while (buffer->total_width < width)
row_buffer_append (buffer, " ", item->attrs);
}
static void
debug_tab_push (const char *message, chtype attrs)
{
// TODO: uh... aren't we rather going to write our own abstraction?
if (g_debug_tab.items_alloc <= g_debug_tab.super.item_count)
{
g_debug_tab.items = xreallocarray (g_debug_tab.items,
sizeof *g_debug_tab.items, (g_debug_tab.items_alloc <<= 1));
}
// TODO: there should be a better, more efficient mechanism for this
struct debug_item *item =
&g_debug_tab.items[g_debug_tab.super.item_count++];
item->text = xstrdup (message);
item->attrs = attrs;
struct timespec tp;
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
item->timestamp = (int64_t) tp.tv_sec * 1000
+ (int64_t) tp.tv_nsec / 1000000;
app_redraw_view ();
} }
static struct tab * static struct tab *
debug_tab_create (void) debug_tab_create (void)
{ {
str_vector_init (&g_debug_tab.lines); g_debug_tab.items = xcalloc
((g_debug_tab.items_alloc = 16), sizeof *g_debug_tab.items);
g_debug_tab.active = true; g_debug_tab.active = true;
struct tab *super = &g_debug_tab.super; struct tab *super = &g_debug_tab.super;
@ -1797,6 +1867,28 @@ debug_tab_create (void)
return super; return super;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
mpd_on_io_hook (void *user_data, bool outgoing, const char *line)
{
(void) user_data;
struct str s;
str_init (&s);
if (outgoing)
{
str_append_printf (&s, "<< %s", line);
debug_tab_push (s.str, APP_ATTR (OUTGOING));
}
else
{
str_append_printf (&s, ">> %s", line);
debug_tab_push (s.str, APP_ATTR (INCOMING));
}
str_free (&s);
}
// --- Initialisation, event handling ------------------------------------------ // --- Initialisation, event handling ------------------------------------------
static void static void
@ -1869,9 +1961,6 @@ static void
app_log_handler (void *user_data, const char *quote, const char *fmt, app_log_handler (void *user_data, const char *quote, const char *fmt,
va_list ap) va_list ap)
{ {
// TODO: we might want to make use of the user_data (attribute?)
(void) user_data;
// We certainly don't want to end up in a possibly infinite recursion // We certainly don't want to end up in a possibly infinite recursion
static bool in_processing; static bool in_processing;
if (in_processing) if (in_processing)
@ -1891,10 +1980,8 @@ app_log_handler (void *user_data, const char *quote, const char *fmt,
fprintf (stderr, "%s\n", message.str); fprintf (stderr, "%s\n", message.str);
else if (g_debug_tab.active) else if (g_debug_tab.active)
{ {
str_vector_add (&g_debug_tab.lines, message.str); debug_tab_push (message.str,
// TODO: there should be a better, more efficient mechanism for this user_data == NULL ? 0 : g_ctx.attrs[(intptr_t) user_data].attrs);
g_debug_tab.super.item_count++;
app_redraw_view ();
} }
else else
{ {