diff options
Diffstat (limited to 'nncmpp.c')
| -rw-r--r-- | nncmpp.c | 217 | 
1 files changed, 163 insertions, 54 deletions
| @@ -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 | 
