Count elapsed seconds
This commit is contained in:
parent
767e87bd3f
commit
6bfa754f4d
217
nncmpp.c
217
nncmpp.c
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user