1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2017 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "screen_lyrics.h"
21 #include "screen_interface.h"
22 #include "screen_status.h"
23 #include "screen_file.h"
24 #include "screen_song.h"
27 #include "mpdclient.h"
30 #include "screen_text.h"
31 #include "screen_utils.h"
44 static struct screen_text text
;
46 static struct mpd_song
*next_song
;
47 static bool follow
= false;
48 /** Set if the cursor position shall be kept during the next lyrics update. */
49 static bool reloading
= false;
52 struct mpd_song
*song
;
54 char *artist
, *title
, *plugin_name
;
56 struct plugin_cycle
*loader
;
62 screen_lyrics_abort(void)
64 if (current
.loader
!= NULL
) {
65 plugin_stop(current
.loader
);
66 current
.loader
= NULL
;
69 if (current
.loader_timeout
!= 0) {
70 g_source_remove(current
.loader_timeout
);
71 current
.loader_timeout
= 0;
74 if (current
.plugin_name
!= NULL
) {
75 g_free(current
.plugin_name
);
76 current
.plugin_name
= NULL
;
79 if (current
.artist
!= NULL
) {
80 g_free(current
.artist
);
81 current
.artist
= NULL
;
84 if (current
.title
!= NULL
) {
85 g_free(current
.title
);
89 if (current
.song
!= NULL
) {
90 mpd_song_free(current
.song
);
96 * Repaint and update the screen, if it is currently active.
99 lyrics_repaint_if_active(void)
101 if (screen_is_visible(&screen_lyrics
)) {
102 screen_text_repaint(&text
);
104 /* XXX repaint the screen title */
109 path_lyr_file(char *path
, size_t size
,
110 const char *artist
, const char *title
)
112 snprintf(path
, size
, "%s/.lyrics/%s - %s.txt",
113 getenv("HOME"), artist
, title
);
117 exists_lyr_file(const char *artist
, const char *title
)
120 path_lyr_file(path
, 1024, artist
, title
);
123 return (stat(path
, &result
) == 0);
127 create_lyr_file(const char *artist
, const char *title
)
130 snprintf(path
, 1024, "%s/.lyrics",
132 mkdir(path
, S_IRWXU
);
134 path_lyr_file(path
, 1024, artist
, title
);
136 return fopen(path
, "w");
142 FILE *lyr_file
= create_lyr_file(current
.artist
, current
.title
);
143 if (lyr_file
== NULL
)
146 for (unsigned i
= 0; i
< text
.lines
->len
; ++i
)
147 fprintf(lyr_file
, "%s\n",
148 (const char*)g_ptr_array_index(text
.lines
, i
));
157 if (!exists_lyr_file(current
.artist
, current
.title
))
161 path_lyr_file(path
, 1024, current
.artist
, current
.title
);
162 if (unlink(path
) != 0)
169 screen_lyrics_set(const GString
*str
)
172 unsigned saved_start
= text
.lw
->start
;
174 screen_text_set(&text
, str
->str
);
176 /* restore the cursor and ensure that it's still valid */
177 text
.lw
->start
= saved_start
;
178 list_window_fetch_cursor(text
.lw
);
180 screen_text_set(&text
, str
->str
);
187 lyrics_repaint_if_active();
191 screen_lyrics_callback(const GString
*result
, const bool success
,
192 const char *plugin_name
, gcc_unused
void *data
)
194 assert(current
.loader
!= NULL
);
196 current
.plugin_name
= g_strdup(plugin_name
);
198 /* Display result, which may be lyrics or error messages */
200 screen_lyrics_set(result
);
202 if (success
== true) {
203 if (options
.lyrics_autosave
&&
204 !exists_lyr_file(current
.artist
, current
.title
))
207 /* translators: no lyrics were found for the song */
208 screen_status_message (_("No lyrics"));
211 if (current
.loader_timeout
!= 0) {
212 g_source_remove(current
.loader_timeout
);
213 current
.loader_timeout
= 0;
216 plugin_stop(current
.loader
);
217 current
.loader
= NULL
;
221 screen_lyrics_timeout_callback(gpointer gcc_unused data
)
223 plugin_stop(current
.loader
);
224 current
.loader
= NULL
;
226 screen_status_printf(_("Lyrics timeout occurred after %d seconds"),
227 options
.lyrics_timeout
);
229 current
.loader_timeout
= 0;
234 screen_lyrics_load(const struct mpd_song
*song
)
236 assert(song
!= NULL
);
238 screen_lyrics_abort();
239 screen_text_clear(&text
);
241 const char *artist
= mpd_song_get_tag(song
, MPD_TAG_ARTIST
, 0);
242 const char *title
= mpd_song_get_tag(song
, MPD_TAG_TITLE
, 0);
244 current
.song
= mpd_song_dup(song
);
245 current
.artist
= g_strdup(artist
);
246 current
.title
= g_strdup(title
);
248 current
.loader
= lyrics_load(current
.artist
, current
.title
,
249 screen_lyrics_callback
, NULL
);
251 if (options
.lyrics_timeout
!= 0) {
252 current
.loader_timeout
=
253 g_timeout_add_seconds(options
.lyrics_timeout
,
254 screen_lyrics_timeout_callback
,
260 screen_lyrics_reload(void)
262 if (current
.loader
== NULL
&& current
.artist
!= NULL
&&
263 current
.title
!= NULL
) {
265 current
.loader
= lyrics_load(current
.artist
, current
.title
,
266 screen_lyrics_callback
, NULL
);
267 screen_text_repaint(&text
);
272 lyrics_screen_init(WINDOW
*w
, unsigned cols
, unsigned rows
)
274 screen_text_init(&text
, w
, cols
, rows
);
278 lyrics_resize(unsigned cols
, unsigned rows
)
280 screen_text_resize(&text
, cols
, rows
);
286 screen_lyrics_abort();
288 screen_text_deinit(&text
);
292 lyrics_open(struct mpdclient
*c
)
294 const struct mpd_song
*next_song_c
=
295 next_song
!= NULL
? next_song
: c
->song
;
297 if (next_song_c
!= NULL
&&
298 (current
.song
== NULL
||
299 strcmp(mpd_song_get_uri(next_song_c
),
300 mpd_song_get_uri(current
.song
)) != 0))
301 screen_lyrics_load(next_song_c
);
303 if (next_song
!= NULL
) {
304 mpd_song_free(next_song
);
310 lyrics_update(struct mpdclient
*c
)
315 if (c
->song
!= NULL
&&
316 (current
.song
== NULL
||
317 strcmp(mpd_song_get_uri(c
->song
),
318 mpd_song_get_uri(current
.song
)) != 0))
319 screen_lyrics_load(c
->song
);
323 lyrics_title(char *str
, size_t size
)
325 if (current
.loader
!= NULL
) {
326 snprintf(str
, size
, "%s (%s)",
328 /* translators: this message is displayed
329 while data is retrieved */
332 } else if (current
.artist
!= NULL
&& current
.title
!= NULL
&&
333 !screen_text_is_empty(&text
)) {
335 n
= snprintf(str
, size
, "%s: %s - %s",
337 current
.artist
, current
.title
);
339 if (options
.lyrics_show_plugin
&& current
.plugin_name
!= NULL
&&
340 (unsigned int) n
< size
- 1)
341 snprintf(str
+ n
, size
- n
, " (%s)",
342 current
.plugin_name
);
352 screen_text_paint(&text
);
355 /* save current lyrics to a file and run editor on it */
359 char *editor
= options
.text_editor
;
360 if (editor
== NULL
) {
361 screen_status_message(_("Editor not configured"));
365 if (options
.text_editor_ask
) {
366 char *buf
= g_strdup_printf(
367 _("Do you really want to start an editor and edit these lyrics [%s/%s]? "),
369 bool really
= screen_get_yesno(buf
, false);
372 screen_status_message(_("Aborted"));
377 if (store_lyr_hd() < 0)
382 /* TODO: fork/exec/wait won't work on Windows, but building a command
383 string for system() is too tricky */
387 screen_status_printf(("%s (%s)"), _("Can't start editor"), g_strerror(errno
));
390 } else if (pid
== 0) {
392 path_lyr_file(path
, sizeof(path
), current
.artist
, current
.title
);
393 execlp(editor
, editor
, path
, NULL
);
394 /* exec failed, do what system does */
399 ret
= waitpid(pid
, &status
, 0);
400 } while (ret
== -1 && errno
== EINTR
);
405 /* TODO: hardly portable */
406 if (WIFEXITED(status
)) {
407 if (WEXITSTATUS(status
) == 0)
408 /* update to get the changes */
409 screen_lyrics_reload();
410 else if (WEXITSTATUS(status
) == 127)
411 screen_status_message(_("Can't start editor"));
413 screen_status_printf(_("Editor exited unexpectedly (%d)"),
414 WEXITSTATUS(status
));
415 } else if (WIFSIGNALED(status
)) {
416 screen_status_printf(_("Editor exited unexpectedly (signal %d)"),
422 lyrics_cmd(struct mpdclient
*c
, command_t cmd
)
424 if (screen_text_cmd(&text
, c
, cmd
))
429 if (current
.loader
!= NULL
) {
430 screen_lyrics_abort();
431 screen_text_clear(&text
);
434 case CMD_SAVE_PLAYLIST
:
435 if (current
.loader
== NULL
&& current
.artist
!= NULL
&&
436 current
.title
!= NULL
&& store_lyr_hd() == 0)
437 /* lyrics for the song were saved on hard disk */
438 screen_status_message (_("Lyrics saved"));
441 if (current
.loader
== NULL
&& current
.artist
!= NULL
&&
442 current
.title
!= NULL
) {
443 switch (delete_lyr_hd()) {
445 screen_status_message (_("Lyrics deleted"));
448 screen_status_message (_("No saved lyrics"));
453 case CMD_LYRICS_UPDATE
:
454 if (c
->song
!= NULL
) {
455 screen_lyrics_load(c
->song
);
456 screen_text_paint(&text
);
463 screen_lyrics_reload();
466 #ifdef ENABLE_SONG_SCREEN
467 case CMD_SCREEN_SONG
:
468 if (current
.song
!= NULL
) {
469 screen_song_switch(c
, current
.song
);
475 case CMD_SCREEN_SWAP
:
476 screen_swap(c
, current
.song
);
480 if (current
.song
!= NULL
) {
481 screen_file_goto_song(c
, current
.song
);
494 const struct screen_functions screen_lyrics
= {
495 .init
= lyrics_screen_init
,
498 .update
= lyrics_update
,
500 .resize
= lyrics_resize
,
501 .paint
= lyrics_paint
,
503 .get_title
= lyrics_title
,
507 screen_lyrics_switch(struct mpdclient
*c
, const struct mpd_song
*song
, bool f
)
509 assert(song
!= NULL
);
512 next_song
= mpd_song_dup(song
);
513 screen_switch(&screen_lyrics
, c
);