13 Commits

Author SHA1 Message Date
61a141203b Bump liberty and version, update NEWS 2021-12-21 05:46:28 +01:00
48482ef2e5 Make incremental search more useful
Make it unanchored, as well as case-insensitive.
2021-12-21 05:46:22 +01:00
840c69767c Prepare a NEWS entry for the next release 2021-12-08 19:05:58 +01:00
a14a907b18 Indicate that a stream download is in progress 2021-12-08 18:58:03 +01:00
333049de01 Do not run cURL fully synchronously
The improvement is very minor in character.
2021-12-08 18:23:30 +01:00
4e3596db35 Add rudimentary incremental search facility 2021-12-08 17:23:25 +01:00
5aa07fd8af Clean up mpd_process_info() better 2021-12-07 20:38:02 +01:00
2060da4a8e Do not jump to beginning after unqueueing
Instead, assume that the whole previously selected range
has been removed, and try to go after or before it accordingly.
2021-12-07 20:34:32 +01:00
f5b5cec340 Clean up unreadable code 2021-12-07 20:10:35 +01:00
1a671dfad5 Document PulseAudio integration 2021-11-16 05:17:15 +01:00
587a02fa15 Indent man page snippets with spaces 2021-11-16 05:16:51 +01:00
227b8e0fa2 Do not show both volumes if unnecessary
Also, make it apparent which value comes from where.
2021-11-16 04:48:52 +01:00
e66e9f249a Rename an action to be shorter
Also, fix make dependencies to include the source file for actions.
2021-11-16 04:48:52 +01:00
6 changed files with 235 additions and 106 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.0) cmake_minimum_required (VERSION 3.0)
project (nncmpp VERSION 1.1.1 LANGUAGES C) project (nncmpp VERSION 1.2.0 LANGUAGES C)
# Moar warnings # Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
@@ -91,9 +91,10 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
# Assuming a Unix-compatible system with a standalone preprocessor # Assuming a Unix-compatible system with a standalone preprocessor
set (actions_list ${PROJECT_SOURCE_DIR}/nncmpp.actions)
set (actions ${PROJECT_BINARY_DIR}/nncmpp-actions.h) set (actions ${PROJECT_BINARY_DIR}/nncmpp-actions.h)
add_custom_command (OUTPUT ${actions} add_custom_command (OUTPUT ${actions}
COMMAND cpp -I${PROJECT_BINARY_DIR} -P ${PROJECT_SOURCE_DIR}/nncmpp.actions COMMAND cpp -I${PROJECT_BINARY_DIR} -P ${actions_list}
| grep . | tr [[\n]] ^ | sed -ne [[h; s/,[^^]*/,/g]] -e [[s/$/COUNT/]] | grep . | tr [[\n]] ^ | sed -ne [[h; s/,[^^]*/,/g]] -e [[s/$/COUNT/]]
-e [[s/[^^]*/\tACTION_&/g]] -e [[s/.*/enum action {\n&\n};\n/p]] -e [[s/[^^]*/\tACTION_&/g]] -e [[s/.*/enum action {\n&\n};\n/p]]
-e [[g; s/,[^^]*//g; y/_/-/]] -e [[s/[^^]\{1,\}/\t"&",/g]] -e [[g; s/,[^^]*//g; y/_/-/]] -e [[s/[^^]\{1,\}/\t"&",/g]]
@@ -102,7 +103,7 @@ add_custom_command (OUTPUT ${actions}
-e [[s/.*/static const char *g_action_descriptions[] = {\n&};/p]] -e [[s/.*/static const char *g_action_descriptions[] = {\n&};/p]]
| tr ^ [[\n]] > ${actions} | tr ^ [[\n]] > ${actions}
COMMAND test -s ${actions} COMMAND test -s ${actions}
DEPENDS ${PROJECT_BINARY_DIR}/config.h VERBATIM) DEPENDS ${actions_list} ${PROJECT_BINARY_DIR}/config.h VERBATIM)
# Build the main executable and link it # Build the main executable and link it
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${actions}) add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${actions})

11
NEWS
View File

@@ -1,3 +1,14 @@
1.2.0 (2021-12-21)
* Added ability to control the volume of MPD's current PulseAudio sink
* Now fetching Internet stream information asynchronously
* Added basic incremental search, normally bound to C-s, in all tabs
* Fixed jumping to the beginning of the queue after deleting items
1.1.1 (2021-11-04) 1.1.1 (2021-11-04)
* Terminal focus in/out events no longer ring the terminall bell * Terminal focus in/out events no longer ring the terminall bell

Submodule liberty updated: 782a9a5977...7e8e085c97

View File

@@ -15,8 +15,8 @@ MPD_PREVIOUS, Previous song
MPD_NEXT, Next song MPD_NEXT, Next song
MPD_BACKWARD, Seek backwards MPD_BACKWARD, Seek backwards
MPD_FORWARD, Seek forwards MPD_FORWARD, Seek forwards
MPD_VOLUME_UP, Increase volume MPD_VOLUME_UP, Increase MPD volume
MPD_VOLUME_DOWN, Decrease volume MPD_VOLUME_DOWN, Decrease MPD volume
MPD_SEARCH, Global search MPD_SEARCH, Global search
MPD_ADD, Add selection to playlist MPD_ADD, Add selection to playlist
@@ -31,13 +31,14 @@ MPD_COMMAND, Send raw command to MPD
#ifdef WITH_PULSE #ifdef WITH_PULSE
PULSE_VOLUME_UP, Increase PulseAudio volume PULSE_VOLUME_UP, Increase PulseAudio volume
PULSE_VOLUME_DOWN, Decrease PulseAudio volume PULSE_VOLUME_DOWN, Decrease PulseAudio volume
PULSE_MUTE, Toggle mute of MPD PulseAudio sink PULSE_MUTE, Toggle PulseAudio sink mute
#endif #endif
CHOOSE, Choose item CHOOSE, Choose item
DELETE, Delete item DELETE, Delete item
UP, Go up a level UP, Go up a level
MULTISELECT, Toggle multiselect MULTISELECT, Toggle multiselect
INCREMENTAL_SEARCH, Incremental search
SCROLL_UP, Scroll up SCROLL_UP, Scroll up
SCROLL_DOWN, Scroll down SCROLL_DOWN, Scroll down

View File

@@ -45,28 +45,27 @@ snippet:
.... ....
settings = { settings = {
address = "~/.mpd/mpd.socket" address = "~/.mpd/mpd.socket"
password = "<your password>" password = "<your password>"
root = "~/Music" pulseaudio = on
pulseaudio = on
} }
colors = { colors = {
normal = "" normal = ""
highlight = "bold" highlight = "bold"
elapsed = "reverse" elapsed = "reverse"
remains = "ul" remains = "ul"
tab_bar = "reverse" tab_bar = "reverse"
tab_active = "ul" tab_active = "ul"
even = "" even = ""
odd = "" odd = ""
selection = "reverse" selection = "reverse"
multiselect = "-1 6" multiselect = "-1 6"
defocused = "ul" defocused = "ul"
scrollbar = "" scrollbar = ""
} }
streams = { streams = {
"dnbradio.com" = "http://www.dnbradio.com/hi.m3u" "dnbradio.com" = "http://www.dnbradio.com/hi.m3u"
"BassDrive.com" = "http://bassdrive.com/v2/streams/BassDrive.pls" "BassDrive.com" = "http://bassdrive.com/v2/streams/BassDrive.pls"
} }
.... ....
@@ -87,17 +86,39 @@ need to set it up manually to match your MPD configuration, e.g.:
.... ....
settings = { settings = {
... ...
spectrum_path = "~/.mpd/mpd.fifo" # "path" spectrum_path = "~/.mpd/mpd.fifo" # "path"
spectrum_format = "44100:16:2" # "format" (samplerate:bits:channels) spectrum_format = "44100:16:2" # "format" (samplerate:bits:channels)
spectrum_bars = 8 # beware of exponential complexity spectrum_bars = 8 # beware of exponential complexity
... ...
} }
.... ....
The sample rate should be greater than 40 kHz, the number of bits 8 or 16, The sample rate should be greater than 40 kHz, the number of bits 8 or 16,
and the number of channels doesn't matter, as they're simply averaged together. and the number of channels doesn't matter, as they're simply averaged together.
PulseAudio
----------
If you find standard MPD volume control useless, you may instead configure
*nncmpp* to show and control the volume of any PulseAudio sink MPD is currently
connected to.
This feature may be enabled with the *settings.pulseaudio* configuration option,
as in the snippet above. To replace the default volume control bindings, use:
....
normal = {
"M-PageUp" = "pulse-volume-up"
"M-PageDown" = "pulse-volume-down"
}
....
The respective actions may also be invoked from the help tab directly.
For this to work, *nncmpp* needs to access the right PulseAudio daemon--in case
your setup is unusual, consult the list of environment variables in
*pulseaudio*(1). MPD-compatibles are currently unsupported.
Files Files
----- -----
*nncmpp* follows the XDG Base Directory Specification. *nncmpp* follows the XDG Base Directory Specification.
@@ -112,4 +133,4 @@ or submit pull requests.
See also See also
-------- --------
*mpd*(1) *mpd*(1), *pulseaudio*(1)

245
nncmpp.c
View File

@@ -154,6 +154,8 @@ xbasename (const char *path)
return last_slash ? last_slash + 1 : path; return last_slash ? last_slash + 1 : path;
} }
static char *xstrdup0 (const char *s) { return s ? xstrdup (s) : NULL; }
static char * static char *
latin1_to_utf8 (const char *latin1) latin1_to_utf8 (const char *latin1)
{ {
@@ -280,6 +282,10 @@ struct poller_curl
struct poller_timer timer; ///< cURL timer struct poller_timer timer; ///< cURL timer
CURLM *multi; ///< cURL multi interface handle CURLM *multi; ///< cURL multi interface handle
struct poller_curl_fd *fds; ///< List of all FDs struct poller_curl_fd *fds; ///< List of all FDs
// TODO: also make sure to dispose of them at the end of the program
int registered; ///< Number of attached easy handles
}; };
static void static void
@@ -388,6 +394,7 @@ poller_curl_init (struct poller_curl *self, struct poller *poller,
|| (mres = curl_multi_setopt (self->multi, CURLMOPT_TIMERDATA, self))) || (mres = curl_multi_setopt (self->multi, CURLMOPT_TIMERDATA, self)))
{ {
curl_multi_cleanup (self->multi); curl_multi_cleanup (self->multi);
self->multi = NULL;
return error_set (e, "%s: %s", return error_set (e, "%s: %s",
"cURL setup failed", curl_multi_strerror (mres)); "cURL setup failed", curl_multi_strerror (mres));
} }
@@ -448,6 +455,7 @@ poller_curl_add (struct poller_curl *self, CURL *easy, struct error **e)
// "CURLMOPT_TIMERFUNCTION [...] will be called from within this function" // "CURLMOPT_TIMERFUNCTION [...] will be called from within this function"
if ((mres = curl_multi_add_handle (self->multi, easy))) if ((mres = curl_multi_add_handle (self->multi, easy)))
return error_set (e, "%s", curl_multi_strerror (mres)); return error_set (e, "%s", curl_multi_strerror (mres));
self->registered++;
return true; return true;
} }
@@ -457,6 +465,7 @@ poller_curl_remove (struct poller_curl *self, CURL *easy, struct error **e)
CURLMcode mres; CURLMcode mres;
if ((mres = curl_multi_remove_handle (self->multi, easy))) if ((mres = curl_multi_remove_handle (self->multi, easy)))
return error_set (e, "%s", curl_multi_strerror (mres)); return error_set (e, "%s", curl_multi_strerror (mres));
self->registered--;
return true; return true;
} }
@@ -1182,6 +1191,7 @@ static struct app_context
// Event loop: // Event loop:
struct poller poller; ///< Poller struct poller poller; ///< Poller
struct poller_curl poller_curl; ///< cURL abstractor
bool quitting; ///< Quit signal for the event loop bool quitting; ///< Quit signal for the event loop
bool polling; ///< The event loop is running bool polling; ///< The event loop is running
@@ -1243,6 +1253,7 @@ static struct app_context
#ifdef WITH_PULSE #ifdef WITH_PULSE
struct pulse pulse; ///< PulseAudio control struct pulse pulse; ///< PulseAudio control
#endif // WITH_PULSE #endif // WITH_PULSE
bool pulse_control_requested; ///< PulseAudio control desired by user
struct line_editor editor; ///< Line editor struct line_editor editor; ///< Line editor
struct poller_idle refresh_event; ///< Refresh the screen struct poller_idle refresh_event; ///< Refresh the screen
@@ -1304,6 +1315,13 @@ on_poll_elapsed_time_changed (struct config_item *item)
g.elapsed_poll = item->value.boolean; g.elapsed_poll = item->value.boolean;
} }
static void
on_pulseaudio_changed (struct config_item *item)
{
// This is only set once, on application startup
g.pulse_control_requested = item->value.boolean;
}
static struct config_schema g_config_settings[] = static struct config_schema g_config_settings[] =
{ {
{ .name = "address", { .name = "address",
@@ -1340,6 +1358,7 @@ static struct config_schema g_config_settings[] =
{ .name = "pulseaudio", { .name = "pulseaudio",
.comment = "Look up MPD in PulseAudio for improved volume controls", .comment = "Look up MPD in PulseAudio for improved volume controls",
.type = CONFIG_ITEM_BOOLEAN, .type = CONFIG_ITEM_BOOLEAN,
.on_change = on_pulseaudio_changed,
.default_ = "off" }, .default_ = "off" },
#endif // WITH_PULSE #endif // WITH_PULSE
@@ -1376,14 +1395,6 @@ get_config_string (struct config_item *root, const char *key)
return item->value.string.str; return item->value.string.str;
} }
static bool
get_config_boolean (struct config_item *root, const char *key)
{
struct config_item *item = config_item_get (root, key, NULL);
hard_assert (item && item->type == CONFIG_ITEM_BOOLEAN);
return item->value.boolean;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void static void
@@ -1497,6 +1508,7 @@ static void
app_init_context (void) app_init_context (void)
{ {
poller_init (&g.poller); poller_init (&g.poller);
hard_assert (poller_curl_init (&g.poller_curl, &g.poller, NULL));
g.client = mpd_client_make (&g.poller); g.client = mpd_client_make (&g.poller);
g.config = config_make (); g.config = config_make ();
g.streams = strv_make (); g.streams = strv_make ();
@@ -1585,6 +1597,7 @@ app_free_context (void)
line_editor_free (&g.editor); line_editor_free (&g.editor);
config_free (&g.config); config_free (&g.config);
poller_curl_free (&g.poller_curl);
poller_free (&g.poller); poller_free (&g.poller);
free (g.message); free (g.message);
@@ -1782,18 +1795,31 @@ app_draw_status (void)
// It gets a bit complicated due to the only right-aligned item on the row // It gets a bit complicated due to the only right-aligned item on the row
struct str volume = str_make (); struct str volume = str_make ();
int remaining = COLS - buf.total_width;
if (g.volume >= 0)
{
str_append (&volume, " ");
#ifdef WITH_PULSE #ifdef WITH_PULSE
if (pulse_volume_status (&g.pulse, &volume)) if (g.pulse_control_requested)
str_append (&volume, " @ "); {
#endif // WITH_PULSE struct str buf = str_make ();
str_append_printf (&volume, "%3d%%", g.volume); if (pulse_volume_status (&g.pulse, &buf))
remaining -= volume.len; {
} if (g.volume >= 0 && g.volume != 100)
str_append_printf (&buf, " (%d%%)", g.volume);
}
else
{
if (g.volume >= 0)
str_append_printf (&buf, "(%d%%)", g.volume);
}
if (buf.len)
str_append_printf (&volume, " %s", buf.str);
str_free (&buf);
}
else
#endif // WITH_PULSE
if (g.volume >= 0)
str_append_printf (&volume, " %3d%%", g.volume);
int remaining = COLS - buf.total_width - volume.len;
if (!stopped && g.song_elapsed >= 0 && g.song_duration >= 1 if (!stopped && g.song_elapsed >= 0 && g.song_duration >= 1
&& remaining > 0) && remaining > 0)
{ {
@@ -2063,6 +2089,8 @@ app_write_mpd_status (struct row_buffer *buf)
row_buffer_append (buf, msg, APP_ATTR (HIGHLIGHT)); row_buffer_append (buf, msg, APP_ATTR (HIGHLIGHT));
free (msg); free (msg);
} }
else if (g.poller_curl.registered)
row_buffer_append (buf, "Downloading...", APP_ATTR (NORMAL));
else if (str_map_find (map, "updating_db")) else if (str_map_find (map, "updating_db"))
row_buffer_append (buf, "Updating database...", APP_ATTR (NORMAL)); row_buffer_append (buf, "Updating database...", APP_ATTR (NORMAL));
else else
@@ -2303,7 +2331,7 @@ app_setvol (int value)
} }
static void static void
app_on_editor_end (bool confirmed) app_on_mpd_command_editor_end (bool confirmed)
{ {
struct mpd_client *c = &g.client; struct mpd_client *c = &g.client;
if (!confirmed) if (!confirmed)
@@ -2318,6 +2346,54 @@ app_on_editor_end (bool confirmed)
mpd_client_idle (c, 0); mpd_client_idle (c, 0);
} }
static size_t
incremental_search_match (const ucs4_t *needle, size_t len,
const struct row_buffer *row)
{
// XXX: this is slow and simplistic, but unistring is awkward to use
size_t best = 0;
for (size_t start = 0; start < row->chars_len; start++)
{
size_t i = 0;
for (; i < len && start + i < row->chars_len; i++)
if (uc_tolower(needle[i]) != uc_tolower(row->chars[start + i].c))
break;
best = MAX (best, i);
}
return best;
}
static void
incremental_search_on_changed (void)
{
struct tab *tab = g.active_tab;
if (!tab->item_count)
return;
size_t best = 0, current = 0, index = MAX (tab->item_selected, 0), i = 0;
while (i++ < tab->item_count)
{
struct row_buffer buf = row_buffer_make ();
tab->on_item_draw (index, &buf, COLS);
current = incremental_search_match (g.editor.line, g.editor.len, &buf);
row_buffer_free (&buf);
if (best < current)
{
best = current;
tab->item_selected = index;
app_move_selection (0);
}
index = (index + 1) % tab->item_count;
}
}
static void
incremental_search_on_end (bool confirmed)
{
(void) confirmed;
// Required callback, nothing to do here.
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static bool static bool
@@ -2360,7 +2436,7 @@ app_process_action (enum action action)
return true; return true;
case ACTION_MPD_COMMAND: case ACTION_MPD_COMMAND:
line_editor_start (&g.editor, ':'); line_editor_start (&g.editor, ':');
g.editor.on_end = app_on_editor_end; g.editor.on_end = app_on_mpd_command_editor_end;
app_invalidate (); app_invalidate ();
return true; return true;
default: default:
@@ -2377,6 +2453,12 @@ app_process_action (enum action action)
else else
tab->item_mark = tab->item_selected; tab->item_mark = tab->item_selected;
return true; return true;
case ACTION_INCREMENTAL_SEARCH:
line_editor_start (&g.editor, '/');
g.editor.on_changed = incremental_search_on_changed;
g.editor.on_end = incremental_search_on_end;
app_invalidate ();
return true;
case ACTION_TAB_LAST: case ACTION_TAB_LAST:
if (!g.last_tab) if (!g.last_tab)
@@ -2667,6 +2749,7 @@ g_normal_defaults[] =
{ "M-Up", ACTION_UP }, { "M-Up", ACTION_UP },
{ "Backspace", ACTION_UP }, { "Backspace", ACTION_UP },
{ "v", ACTION_MULTISELECT }, { "v", ACTION_MULTISELECT },
{ "C-s", ACTION_INCREMENTAL_SEARCH },
{ "/", ACTION_MPD_SEARCH }, { "/", ACTION_MPD_SEARCH },
{ "a", ACTION_MPD_ADD }, { "a", ACTION_MPD_ADD },
{ "r", ACTION_MPD_REPLACE }, { "r", ACTION_MPD_REPLACE },
@@ -3446,8 +3529,8 @@ struct stream_tab_task
{ {
struct poller_curl_task curl; ///< Superclass struct poller_curl_task curl; ///< Superclass
struct str data; ///< Downloaded data struct str data; ///< Downloaded data
bool polling; ///< Still downloading
bool replace; ///< Should playlist be replaced? bool replace; ///< Should playlist be replaced?
struct curl_slist *alias_ok;
}; };
static bool static bool
@@ -3531,24 +3614,39 @@ streams_tab_extract_links (struct str *data, const char *content_type,
return true; return true;
} }
static void
streams_tab_task_finalize (struct stream_tab_task *self)
{
curl_easy_cleanup (self->curl.easy);
curl_slist_free_all (self->alias_ok);
str_free (&self->data);
free (self);
}
static void
streams_tab_task_dispose (struct stream_tab_task *self)
{
hard_assert (poller_curl_remove (&g.poller_curl, self->curl.easy, NULL));
streams_tab_task_finalize (self);
}
static void static void
streams_tab_on_downloaded (CURLMsg *msg, struct poller_curl_task *task) streams_tab_on_downloaded (CURLMsg *msg, struct poller_curl_task *task)
{ {
struct stream_tab_task *self = struct stream_tab_task *self =
CONTAINER_OF (task, struct stream_tab_task, curl); CONTAINER_OF (task, struct stream_tab_task, curl);
self->polling = false;
if (msg->data.result if (msg->data.result
&& msg->data.result != CURLE_WRITE_ERROR) && msg->data.result != CURLE_WRITE_ERROR)
{ {
cstr_uncapitalize (self->curl.curl_error); cstr_uncapitalize (self->curl.curl_error);
print_error ("%s", self->curl.curl_error); print_error ("%s", self->curl.curl_error);
return; goto dispose;
} }
struct mpd_client *c = &g.client; struct mpd_client *c = &g.client;
if (c->state != MPD_CONNECTED) if (c->state != MPD_CONNECTED)
return; goto dispose;
CURL *easy = msg->easy_handle; CURL *easy = msg->easy_handle;
CURLcode res; CURLcode res;
@@ -3561,13 +3659,13 @@ streams_tab_on_downloaded (CURLMsg *msg, struct poller_curl_task *task)
{ {
print_error ("%s: %s", print_error ("%s: %s",
"cURL info retrieval failed", curl_easy_strerror (res)); "cURL info retrieval failed", curl_easy_strerror (res));
return; goto dispose;
} }
// cURL is not willing to parse the ICY header, the code is zero then // cURL is not willing to parse the ICY header, the code is zero then
if (code && code != 200) if (code && code != 200)
{ {
print_error ("%s: %ld", "unexpected HTTP response", code); print_error ("%s: %ld", "unexpected HTTP response", code);
return; goto dispose;
} }
mpd_client_list_begin (c); mpd_client_list_begin (c);
@@ -3586,6 +3684,9 @@ streams_tab_on_downloaded (CURLMsg *msg, struct poller_curl_task *task)
mpd_client_list_end (c); mpd_client_list_end (c);
mpd_client_add_task (c, mpd_on_simple_response, NULL); mpd_client_add_task (c, mpd_on_simple_response, NULL);
mpd_client_idle (c, 0); mpd_client_idle (c, 0);
dispose:
streams_tab_task_dispose (self);
} }
static size_t static size_t
@@ -3604,60 +3705,44 @@ write_callback (char *ptr, size_t size, size_t nmemb, void *user_data)
static bool static bool
streams_tab_process (const char *uri, bool replace, struct error **e) streams_tab_process (const char *uri, bool replace, struct error **e)
{ {
struct poller poller; // TODO: streams_tab_task_dispose() on that running task
poller_init (&poller); if (g.poller_curl.registered)
{
print_error ("waiting for the last stream to time out");
return false;
}
struct poller_curl pc; struct stream_tab_task *task = xcalloc (1, sizeof *task);
hard_assert (poller_curl_init (&pc, &poller, NULL)); hard_assert (poller_curl_spawn (&task->curl, NULL));
struct stream_tab_task task;
hard_assert (poller_curl_spawn (&task.curl, NULL));
CURL *easy = task.curl.easy; CURL *easy = task->curl.easy;
task.data = str_make (); task->data = str_make ();
task.replace = replace; task->replace = replace;
bool result = false; task->alias_ok = curl_slist_append (NULL, "ICY 200 OK");
struct curl_slist *ok_headers = curl_slist_append (NULL, "ICY 200 OK");
CURLcode res; CURLcode res;
if ((res = curl_easy_setopt (easy, CURLOPT_FOLLOWLOCATION, 1L)) if ((res = curl_easy_setopt (easy, CURLOPT_FOLLOWLOCATION, 1L))
|| (res = curl_easy_setopt (easy, CURLOPT_NOPROGRESS, 1L)) || (res = curl_easy_setopt (easy, CURLOPT_NOPROGRESS, 1L))
// TODO: make the timeout a bit larger once we're asynchronous || (res = curl_easy_setopt (easy, CURLOPT_TIMEOUT, 10L))
|| (res = curl_easy_setopt (easy, CURLOPT_TIMEOUT, 5L))
// Not checking anything, we just want some data, any data // Not checking anything, we just want some data, any data
|| (res = curl_easy_setopt (easy, CURLOPT_SSL_VERIFYPEER, 0L)) || (res = curl_easy_setopt (easy, CURLOPT_SSL_VERIFYPEER, 0L))
|| (res = curl_easy_setopt (easy, CURLOPT_SSL_VERIFYHOST, 0L)) || (res = curl_easy_setopt (easy, CURLOPT_SSL_VERIFYHOST, 0L))
|| (res = curl_easy_setopt (easy, CURLOPT_URL, uri)) || (res = curl_easy_setopt (easy, CURLOPT_URL, uri))
|| (res = curl_easy_setopt (easy, CURLOPT_HTTP200ALIASES, ok_headers)) || (res = curl_easy_setopt (easy, CURLOPT_HTTP200ALIASES, task->alias_ok))
|| (res = curl_easy_setopt (easy, CURLOPT_VERBOSE, (long) g_debug_mode)) || (res = curl_easy_setopt (easy, CURLOPT_VERBOSE, (long) g_debug_mode))
|| (res = curl_easy_setopt (easy, CURLOPT_DEBUGFUNCTION, print_curl_debug)) || (res = curl_easy_setopt (easy, CURLOPT_DEBUGFUNCTION, print_curl_debug))
|| (res = curl_easy_setopt (easy, CURLOPT_WRITEDATA, &task.data)) || (res = curl_easy_setopt (easy, CURLOPT_WRITEDATA, &task->data))
|| (res = curl_easy_setopt (easy, CURLOPT_WRITEFUNCTION, write_callback))) || (res = curl_easy_setopt (easy, CURLOPT_WRITEFUNCTION, write_callback)))
{ {
error_set (e, "%s: %s", "cURL setup failed", curl_easy_strerror (res)); error_set (e, "%s: %s", "cURL setup failed", curl_easy_strerror (res));
goto error; streams_tab_task_finalize (task);
return false;
} }
task.curl.on_done = streams_tab_on_downloaded; task->curl.on_done = streams_tab_on_downloaded;
hard_assert (poller_curl_add (&pc, task.curl.easy, NULL)); hard_assert (poller_curl_add (&g.poller_curl, task->curl.easy, NULL));
return true;
// TODO: don't run a subloop, run the task fully asynchronously
task.polling = true;
while (task.polling)
poller_run (&poller);
hard_assert (poller_curl_remove (&pc, task.curl.easy, NULL));
result = true;
error:
curl_easy_cleanup (task.curl.easy);
curl_slist_free_all (ok_headers);
str_free (&task.data);
poller_curl_free (&pc);
poller_free (&poller);
return result;
} }
static bool static bool
@@ -4173,7 +4258,7 @@ static void
pulse_update (void) pulse_update (void)
{ {
struct mpd_client *c = &g.client; struct mpd_client *c = &g.client;
if (!get_config_boolean (g.config.root, "settings.pulseaudio")) if (!g.pulse_control_requested)
return; return;
// The read permission is sufficient for this command // The read permission is sufficient for this command
@@ -4350,7 +4435,7 @@ mpd_find_pos_of_id (const char *desired_id)
return -1; return -1;
} }
static char * static const char *
mpd_id_of_pos (int pos) mpd_id_of_pos (int pos)
{ {
compact_map_t map = item_list_get (&g.playlist, pos); compact_map_t map = item_list_get (&g.playlist, pos);
@@ -4360,29 +4445,39 @@ mpd_id_of_pos (int pos)
static void static void
mpd_process_info (const struct strv *data) mpd_process_info (const struct strv *data)
{ {
int *selected = &g_current_tab.item_selected; struct tab *tab = &g_current_tab;
int *marked = &g_current_tab.item_mark; char *prev_sel_id = xstrdup0 (mpd_id_of_pos (tab->item_selected));
char *prev_sel_id = mpd_id_of_pos (*selected); char *prev_mark_id = xstrdup0 (mpd_id_of_pos (tab->item_mark));
char *prev_mark_id = mpd_id_of_pos (*marked); char *fallback_id = NULL;
if (prev_sel_id) prev_sel_id = xstrdup (prev_sel_id);
if (prev_mark_id) prev_mark_id = xstrdup (prev_mark_id); struct tab_range r = tab_selection_range (g.active_tab);
if (r.upto >= 0)
{
if (!(fallback_id = xstrdup0 (mpd_id_of_pos (r.upto + 1))))
fallback_id = xstrdup0 (mpd_id_of_pos (r.from - 1));
}
mpd_process_info_data (data); mpd_process_info_data (data);
const char *sel_id = mpd_id_of_pos (*selected); const char *sel_id = mpd_id_of_pos (tab->item_selected);
const char *mark_id = mpd_id_of_pos (*marked); const char *mark_id = mpd_id_of_pos (tab->item_mark);
if (prev_mark_id && (!mark_id || strcmp (prev_mark_id, mark_id))) if (prev_mark_id && (!mark_id || strcmp (prev_mark_id, mark_id)))
*marked = mpd_find_pos_of_id (prev_mark_id); tab->item_mark = mpd_find_pos_of_id (prev_mark_id);
if (prev_sel_id && (!sel_id || strcmp (prev_sel_id, sel_id))) if (prev_sel_id && (!sel_id || strcmp (prev_sel_id, sel_id)))
{ {
if ((*selected = mpd_find_pos_of_id (prev_sel_id)) < 0) if ((tab->item_selected = mpd_find_pos_of_id (prev_sel_id)) < 0)
*marked = -1; {
tab->item_mark = -1;
if (fallback_id)
tab->item_selected = mpd_find_pos_of_id (fallback_id);
}
app_move_selection (0); app_move_selection (0);
} }
free (prev_sel_id); free (prev_sel_id);
free (prev_mark_id); free (prev_mark_id);
free (fallback_id);
} }
static void static void