Count elapsed seconds

This commit is contained in:
Přemysl Eric Janouch 2016-10-01 23:43:51 +02:00
parent 767e87bd3f
commit 6bfa754f4d
Signed by: p
GPG Key ID: B715679E3A361BE6
1 changed files with 163 additions and 54 deletions

217
nncmpp.c
View File

@ -70,7 +70,7 @@
#define CTRL_KEY(x) ((x) - 'A' + 1)
#define APP_TITLE PROGRAM_NAME " " ///< Left top corner
#define APP_TITLE PROGRAM_NAME ///< Left top corner
// --- Utilities ---------------------------------------------------------------
@ -101,17 +101,19 @@ update_curses_terminal_size (void)
// global namespace and makes it harder to distinguish what functions relate to.
// Avoiding colours in the defaults here in order to support dumb terminals
// TODO: we also want a different "highlighted" attribute for the top part
// -> then we have to do attrset(0)
// TODO: add two attributes for the gauge
// -> we should also make it possible to set characters for both parts
// TODO: create another attribute for selected items
#define ATTRIBUTE_TABLE(XX) \
XX( TOP, "top", -1, -1, 0 ) \
XX( HEADER, "header", -1, -1, A_REVERSE ) \
XX( ACTIVE, "header_active", -1, -1, A_UNDERLINE ) \
XX( EVEN, "even", -1, -1, 0 ) \
XX( ODD, "odd", -1, -1, 0 )
#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 )
enum
{
@ -202,11 +204,11 @@ static struct app_context
enum player_state state; ///< Player state
struct str_map song_info; ///< Current song info
// FIXME: this is doomed to drift unless we use POSIX CLOCK_MONOTONIC
struct poller_timer elapsed_event; ///< Seconds elapsed event
// TODO: initialize these to -1
int song_elapsed; ///< Song elapsed in seconds
int song_duration; ///< Song duration in seconds
int volume; ///< Current volume
// Data:
@ -690,9 +692,35 @@ app_next_row (chtype attrs)
mvwhline (stdscr, g_ctx.list_offset++, 0, ' ' | attrs, COLS);
}
static size_t
app_write_time (int seconds, chtype attrs)
{
int minutes = seconds / 60; seconds %= 60;
int hours = minutes / 60; hours %= 60;
struct str s;
str_init (&s);
if (hours)
{
str_append_printf (&s, "%d:", hours);
str_append_printf (&s, "%02d:", minutes);
}
else
str_append_printf (&s, "%d:", minutes);
str_append_printf (&s, "%02d", seconds);
size_t result = app_write_utf8 (s.str, attrs, -1);
str_free (&s);
return result;
}
static void
app_redraw_status (void)
{
chtype normal = APP_ATTR (HEADER);
chtype highlight = APP_ATTR (HIGHLIGHT);
if (g_ctx.state == PLAYER_STOPPED)
goto line;
@ -706,15 +734,22 @@ app_redraw_status (void)
|| (title = str_map_find (map, "name"))
|| (title = str_map_find (map, "file")))
{
app_next_row (0);
app_write_utf8 (title, A_BOLD, COLS);
app_next_row (normal);
struct row_buffer buf;
row_buffer_init (&buf);
row_buffer_append (&buf, title, highlight);
if (buf.total_width > COLS)
row_buffer_ellipsis (&buf, COLS, highlight);
row_buffer_flush (&buf);
row_buffer_free (&buf);
}
char *artist = str_map_find (map, "artist");
char *album = str_map_find (map, "album");
if (artist || album)
{
app_next_row (0);
app_next_row (normal);
struct row_buffer buf;
row_buffer_init (&buf);
@ -722,45 +757,76 @@ app_redraw_status (void)
bool first = true;
if (artist)
{
if (!first) row_buffer_append (&buf, " ", 0);
row_buffer_append (&buf, "by ", 0);
row_buffer_append (&buf, artist, A_BOLD);
if (!first) row_buffer_append (&buf, " ", normal);
row_buffer_append (&buf, "by ", normal);
row_buffer_append (&buf, artist, highlight);
first = false;
}
if (album)
{
if (!first) row_buffer_append (&buf, " ", 0);
row_buffer_append (&buf, "from ", 0);
row_buffer_append (&buf, album, A_BOLD);
if (!first) row_buffer_append (&buf, " ", normal);
row_buffer_append (&buf, "from ", normal);
row_buffer_append (&buf, album, highlight);
first = false;
}
if (buf.total_width > COLS)
row_buffer_ellipsis (&buf, COLS, 0);
row_buffer_ellipsis (&buf, COLS, normal);
row_buffer_flush (&buf);
row_buffer_free (&buf);
}
line:
app_next_row (0);
app_next_row (normal);
bool stopped = g_ctx.state == PLAYER_STOPPED;
app_write_utf8 ("<< ", stopped ? 0 : A_BOLD, -1);
chtype active = stopped ? normal : highlight;
// TODO: we desperately need some better abstractions for this;
// at minimum we need to count characters already written
app_write_utf8 ("<<", active, -1);
addch (' ' | normal);
if (g_ctx.state == PLAYER_PLAYING)
app_write_utf8 ("|| ", A_BOLD, -1);
app_write_utf8 ("||", highlight, -1);
else
app_write_utf8 ("|> ", A_BOLD, -1);
app_write_utf8 ("|>", highlight, -1);
addch (' ' | normal);
app_write_utf8 ("[] ", stopped ? 0 : A_BOLD, -1);
app_write_utf8 (">> ", stopped ? 0 : A_BOLD, -1);
app_write_utf8 ("[]", active, -1);
addch (' ' | normal);
app_write_utf8 (">>", active, -1);
addch (' ' | normal);
addch (' ' | normal);
if (stopped)
app_write_utf8 ("Stopped", 0, COLS);
app_write_utf8 ("Stopped", normal, COLS);
else
{
// TODO: convert and display "song_elapsed / song_duration"
// TODO: display a gauge representing the same information
// TODO: convert the display to minutes
if (g_ctx.song_elapsed >= 0)
{
app_write_time (g_ctx.song_elapsed, normal);
addch (' ' | normal);
}
if (g_ctx.song_duration >= 0)
{
addch ('/' | normal);
addch (' ' | normal);
app_write_time (g_ctx.song_duration, normal);
addch (' ' | normal);
}
addch (' ' | normal);
if (g_ctx.song_elapsed >= 0 && g_ctx.song_duration >= 0)
{
// TODO: display a gauge representing the same information,
// attributes BAR_ELAPSED and BAR_REMAINS
}
else
{
// TODO: just fill the space
}
}
// TODO: append the volume value if available
@ -772,32 +838,35 @@ app_redraw_top (void)
// TODO: when it changes from the previous value, fix the selection
g_ctx.list_offset = 0;
attrset (APP_ATTR (TOP));
attrset (0);
switch (g_ctx.client.state)
{
case MPD_CONNECTED:
app_redraw_status ();
break;
case MPD_CONNECTING:
app_next_row (0);
app_write_utf8 ("Connecting to MPD...", 0, COLS);
app_next_row (APP_ATTR (HEADER));
app_write_utf8 ("Connecting to MPD...", APP_ATTR (HEADER), COLS);
break;
case MPD_DISCONNECTED:
app_next_row (0);
app_write_utf8 ("Disconnected", 0, COLS);
app_next_row (APP_ATTR (HEADER));
app_write_utf8 ("Disconnected", APP_ATTR (HEADER), COLS);
}
attrset (APP_ATTR (HEADER));
attrset (APP_ATTR (TAB_BAR));
app_next_row (0);
// TODO: render this with APP_ATTR (ACTIVE) when the help tab is selected;
// TODO: render this with APP_ATTR (TAB_ACTIVE) when the help tab is selected;
// ...maybe the help tab should not even be on the list?
size_t indent = app_write_utf8 (APP_TITLE, A_BOLD, -1);
size_t indent = app_write_utf8 (APP_TITLE, 0, -1);
addch (' ');
indent++;
attrset (0);
LIST_FOR_EACH (struct tab, it, g_ctx.tabs)
{
indent += app_write_utf8 (it->name,
it == g_ctx.active_tab ? APP_ATTR (ACTIVE) : APP_ATTR (HEADER),
it == g_ctx.active_tab ? APP_ATTR (TAB_ACTIVE) : APP_ATTR (TAB_BAR),
MIN (COLS - indent, it->name_width));
}
refresh ();
@ -815,10 +884,10 @@ app_redraw_view (void)
(int) tab->item_count - tab->item_top);
for (int row_index = 0; row_index < to_show; row_index++)
{
unsigned item_index = tab->item_top + row_index;
int item_index = tab->item_top + row_index;
int row_attrs = (item_index & 1) ? APP_ATTR (ODD) : APP_ATTR (EVEN);
if ((int) item_index == tab->item_selected)
row_attrs |= A_REVERSE;
if (item_index == tab->item_selected)
row_attrs = APP_ATTR (SELECTION);
attrset (row_attrs);
@ -1246,11 +1315,18 @@ mpd_on_info_response (const struct mpd_response *response,
const struct str_vector *data, void *user_data)
{
(void) user_data;
// TODO: do this also on disconnect
g_ctx.song_elapsed = -1;
g_ctx.song_duration = -1;
g_ctx.volume = -1;
str_map_free (&g_ctx.song_info);
poller_timer_reset (&g_ctx.elapsed_event);
if (!response->success)
{
print_debug ("%s: %s",
"retrieving MPD info failed", response->message_text);
// TODO: invalidate any song-related data
return;
}
@ -1269,24 +1345,54 @@ mpd_on_info_response (const struct mpd_response *response,
// Note that we may receive a "time" field twice, however the right one
// wins here due to the order we send the commands in
char *time = str_map_find (&map, "time");
char *time = str_map_find (&map, "time");
char *duration = str_map_find (&map, "duration");
if (time)
{
char *colon = strchr (time, ':');
// TODO: split "time" at ':' -> elapsed seconds, total seconds;
// if there's no colon, use "duration"
if (colon)
{
*colon = '\0';
duration = colon + 1;
}
}
// TODO: if we're playing, parse the "elapsed" value and use it
// to set a timer for status updates (cancel the timer at the start
// of the info callback and upon disconnect)
unsigned long tmp;
if (time && xstrtoul (&tmp, time, 10))
g_ctx.song_elapsed = tmp;
if (duration && xstrtoul (&tmp, duration, 10))
g_ctx.song_duration = tmp;
// TODO: "volume" is a string, parse it nonetheless so that we can later
// tell MPD to change it
// TODO: use "time" as a fallback (no milliseconds there)
char *elapsed = str_map_find (&map, "elapsed");
if (elapsed && g_ctx.state == PLAYER_PLAYING)
{
// TODO: parse the "elapsed" value and use it
char *period = strchr (elapsed, '.');
if (period && xstrtoul (&tmp, period + 1, 10))
{
// TODO: initialize the timer and create a callback
poller_timer_set (&g_ctx.elapsed_event, 1000 - tmp);
}
}
char *volume = str_map_find (&map, "volume");
if (volume && xstrtoul (&tmp, volume, 10))
g_ctx.volume = tmp;
str_map_free (&g_ctx.song_info);
g_ctx.song_info = map;
app_redraw ();
}
static void
mpd_on_tick (void *user_data)
{
(void) user_data;
// FIXME: this is doomed to drift unless we use POSIX CLOCK_MONOTONIC
poller_timer_set (&g_ctx.elapsed_event, 1000);
g_ctx.song_elapsed++;
// TODO: try to be more efficient in the redrawing procedures
app_redraw ();
}
@ -1521,6 +1627,9 @@ app_init_poller_events (void)
poller_timer_init (&g_ctx.reconnect_event, &g_ctx.poller);
g_ctx.reconnect_event.dispatcher = app_on_reconnect;
poller_timer_set (&g_ctx.reconnect_event, 0);
poller_timer_init (&g_ctx.elapsed_event, &g_ctx.poller);
g_ctx.elapsed_event.dispatcher = mpd_on_tick;
}
int