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_queue.h"
21 #include "screen_interface.h"
22 #include "screen_file.h"
23 #include "screen_status.h"
24 #include "screen_find.h"
29 #include "mpdclient.h"
33 #include "song_paint.h"
35 #include "screen_utils.h"
36 #include "screen_song.h"
37 #include "screen_lyrics.h"
44 #include <mpd/client.h>
50 #define MAX_SONG_LENGTH 512
58 } completion_callback_data_t
;
60 static struct hscroll hscroll
;
63 static struct mpdclient_playlist
*playlist
;
64 static int current_song_id
= -1;
65 static int selected_song_id
= -1;
66 static struct list_window
*lw
;
67 static guint timer_hide_cursor_id
;
70 screen_queue_paint(void);
73 screen_queue_repaint(void)
79 static const struct mpd_song
*
80 screen_queue_selected_song(void)
82 return !lw
->range_selection
&&
83 lw
->selected
< playlist_length(playlist
)
84 ? playlist_get(playlist
, lw
->selected
)
89 screen_queue_save_selection(void)
91 selected_song_id
= screen_queue_selected_song() != NULL
92 ? (int)mpd_song_get_id(screen_queue_selected_song())
97 screen_queue_restore_selection(void)
99 list_window_set_length(lw
, playlist_length(playlist
));
101 if (selected_song_id
< 0)
102 /* there was no selection */
105 const struct mpd_song
*song
= screen_queue_selected_song();
107 mpd_song_get_id(song
) == (unsigned)selected_song_id
)
108 /* selection is still valid */
111 int pos
= playlist_get_index_from_id(playlist
, selected_song_id
);
113 list_window_set_cursor(lw
, pos
);
115 screen_queue_save_selection();
119 screen_queue_lw_callback(unsigned idx
, gcc_unused
void *data
)
121 static char songname
[MAX_SONG_LENGTH
];
123 assert(playlist
!= NULL
);
124 assert(idx
< playlist_length(playlist
));
126 struct mpd_song
*song
= playlist_get(playlist
, idx
);
128 strfsong(songname
, MAX_SONG_LENGTH
, options
.list_format
, song
);
134 center_playing_item(const struct mpd_status
*status
, bool center_cursor
)
136 if (status
== NULL
||
137 (mpd_status_get_state(status
) != MPD_STATE_PLAY
&&
138 mpd_status_get_state(status
) != MPD_STATE_PAUSE
))
141 /* try to center the song that are playing */
142 int idx
= mpd_status_get_song_pos(status
);
146 list_window_center(lw
, idx
);
149 list_window_set_cursor(lw
, idx
);
153 /* make sure the cursor is in the window */
154 list_window_fetch_cursor(lw
);
159 get_current_song_id(const struct mpd_status
*status
)
161 return status
!= NULL
&&
162 (mpd_status_get_state(status
) == MPD_STATE_PLAY
||
163 mpd_status_get_state(status
) == MPD_STATE_PAUSE
)
164 ? (int)mpd_status_get_song_id(status
)
169 screen_queue_song_change(const struct mpd_status
*status
)
171 if (get_current_song_id(status
) == current_song_id
)
174 current_song_id
= get_current_song_id(status
);
176 /* center the cursor */
177 if (options
.auto_center
&& !lw
->range_selection
)
178 center_playing_item(status
, false);
185 save_pre_completion_cb(GCompletion
*gcmp
, gcc_unused gchar
*line
,
188 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
189 GList
**list
= tmp
->list
;
190 struct mpdclient
*c
= tmp
->c
;
192 if( *list
== NULL
) {
193 /* create completion list */
194 *list
= gcmp_list_from_path(c
, "", NULL
, GCMP_TYPE_PLAYLIST
);
195 g_completion_add_items(gcmp
, *list
);
200 save_post_completion_cb(gcc_unused GCompletion
*gcmp
,
201 gcc_unused gchar
*line
, GList
*items
,
202 gcc_unused
void *data
)
204 if (g_list_length(items
) >= 1)
205 screen_display_completion_list(items
);
211 * Wrapper for strncmp(). We are not allowed to pass &strncmp to
212 * g_completion_set_compare(), because strncmp() takes size_t where
213 * g_completion_set_compare passes a gsize value.
216 completion_strncmp(const gchar
*s1
, const gchar
*s2
, gsize n
)
218 return strncmp(s1
, s2
, n
);
223 playlist_save(struct mpdclient
*c
, char *name
, char *defaultname
)
225 struct mpd_connection
*connection
;
234 /* initialize completion support */
235 GCompletion
*gcmp
= g_completion_new(NULL
);
236 g_completion_set_compare(gcmp
, completion_strncmp
);
238 completion_callback_data_t data
= {
243 wrln_completion_callback_data
= &data
;
244 wrln_pre_completion_callback
= save_pre_completion_cb
;
245 wrln_post_completion_callback
= save_post_completion_cb
;
248 /* query the user for a filename */
249 filename
= screen_readln(_("Save queue as"),
254 /* destroy completion support */
255 wrln_completion_callback_data
= NULL
;
256 wrln_pre_completion_callback
= NULL
;
257 wrln_post_completion_callback
= NULL
;
258 g_completion_free(gcmp
);
259 list
= string_list_free(list
);
261 filename
=g_strstrip(filename
);
264 filename
=g_strdup(name
);
266 if (filename
== NULL
)
269 /* send save command to mpd */
271 connection
= mpdclient_get_connection(c
);
272 if (connection
== NULL
) {
277 char *filename_utf8
= locale_to_utf8(filename
);
278 if (!mpd_run_save(connection
, filename_utf8
)) {
279 if (mpd_connection_get_error(connection
) == MPD_ERROR_SERVER
&&
280 mpd_connection_get_server_error(connection
) == MPD_SERVER_ERROR_EXIST
&&
281 mpd_connection_clear_error(connection
)) {
282 char *buf
= g_strdup_printf(_("Replace %s [%s/%s] ? "),
284 bool replace
= screen_get_yesno(buf
, false);
288 g_free(filename_utf8
);
290 screen_status_printf(_("Aborted"));
294 if (!mpd_run_rm(connection
, filename_utf8
) ||
295 !mpd_run_save(connection
, filename_utf8
)) {
296 mpdclient_handle_error(c
);
297 g_free(filename_utf8
);
302 mpdclient_handle_error(c
);
303 g_free(filename_utf8
);
309 c
->events
|= MPD_IDLE_STORED_PLAYLIST
;
311 g_free(filename_utf8
);
314 screen_status_printf(_("Saved %s"), filename
);
320 static void add_dir(GCompletion
*gcmp
, gchar
*dir
, GList
**dir_list
,
321 GList
**list
, struct mpdclient
*c
)
323 g_completion_remove_items(gcmp
, *list
);
324 *list
= string_list_remove(*list
, dir
);
325 *list
= gcmp_list_from_path(c
, dir
, *list
, GCMP_TYPE_RFILE
);
326 g_completion_add_items(gcmp
, *list
);
327 *dir_list
= g_list_append(*dir_list
, g_strdup(dir
));
330 static void add_pre_completion_cb(GCompletion
*gcmp
, gchar
*line
, void *data
)
332 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
333 GList
**dir_list
= tmp
->dir_list
;
334 GList
**list
= tmp
->list
;
335 struct mpdclient
*c
= tmp
->c
;
338 /* create initial list */
339 *list
= gcmp_list_from_path(c
, "", NULL
, GCMP_TYPE_RFILE
);
340 g_completion_add_items(gcmp
, *list
);
341 } else if (line
&& line
[0] && line
[strlen(line
)-1]=='/' &&
342 string_list_find(*dir_list
, line
) == NULL
) {
343 /* add directory content to list */
344 add_dir(gcmp
, line
, dir_list
, list
, c
);
348 static void add_post_completion_cb(GCompletion
*gcmp
, gchar
*line
,
349 GList
*items
, void *data
)
351 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
352 GList
**dir_list
= tmp
->dir_list
;
353 GList
**list
= tmp
->list
;
354 struct mpdclient
*c
= tmp
->c
;
356 if (g_list_length(items
) >= 1)
357 screen_display_completion_list(items
);
359 if (line
&& line
[0] && line
[strlen(line
) - 1] == '/' &&
360 string_list_find(*dir_list
, line
) == NULL
) {
361 /* add directory content to list */
362 add_dir(gcmp
, line
, dir_list
, list
, c
);
368 handle_add_to_playlist(struct mpdclient
*c
)
371 /* initialize completion support */
372 GCompletion
*gcmp
= g_completion_new(NULL
);
373 g_completion_set_compare(gcmp
, completion_strncmp
);
376 GList
*dir_list
= NULL
;
377 completion_callback_data_t data
= {
379 .dir_list
= &dir_list
,
383 wrln_completion_callback_data
= &data
;
384 wrln_pre_completion_callback
= add_pre_completion_cb
;
385 wrln_post_completion_callback
= add_post_completion_cb
;
387 GCompletion
*gcmp
= NULL
;
391 char *path
= screen_readln(_("Add"),
396 /* destroy completion data */
398 wrln_completion_callback_data
= NULL
;
399 wrln_pre_completion_callback
= NULL
;
400 wrln_post_completion_callback
= NULL
;
401 g_completion_free(gcmp
);
402 string_list_free(list
);
403 string_list_free(dir_list
);
406 /* add the path to the playlist */
408 char *path_utf8
= locale_to_utf8(path
);
409 mpdclient_cmd_add_path(c
, path_utf8
);
418 screen_queue_init(WINDOW
*w
, unsigned cols
, unsigned rows
)
420 lw
= list_window_init(w
, cols
, rows
);
424 hscroll_init(&hscroll
, w
, options
.scroll_sep
);
429 timer_hide_cursor(gpointer data
)
431 struct mpdclient
*c
= data
;
433 assert(options
.hide_cursor
> 0);
434 assert(timer_hide_cursor_id
!= 0);
436 timer_hide_cursor_id
= 0;
438 /* hide the cursor when mpd is playing and the user is inactive */
440 if (c
->status
!= NULL
&&
441 mpd_status_get_state(c
->status
) == MPD_STATE_PLAY
) {
442 lw
->hide_cursor
= true;
443 screen_queue_repaint();
445 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
446 timer_hide_cursor
, c
);
452 screen_queue_open(struct mpdclient
*c
)
454 playlist
= &c
->playlist
;
456 assert(timer_hide_cursor_id
== 0);
457 if (options
.hide_cursor
> 0) {
458 lw
->hide_cursor
= false;
459 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
460 timer_hide_cursor
, c
);
463 screen_queue_restore_selection();
464 screen_queue_song_change(c
->status
);
468 screen_queue_close(void)
470 if (timer_hide_cursor_id
!= 0) {
471 g_source_remove(timer_hide_cursor_id
);
472 timer_hide_cursor_id
= 0;
477 hscroll_clear(&hscroll
);
482 screen_queue_resize(unsigned cols
, unsigned rows
)
484 list_window_resize(lw
, cols
, rows
);
489 screen_queue_exit(void)
491 list_window_free(lw
);
495 screen_queue_title(char *str
, size_t size
)
497 if (options
.host
== NULL
)
500 g_snprintf(str
, size
, _("Queue on %s"), options
.host
);
505 screen_queue_paint_callback(WINDOW
*w
, unsigned i
,
506 unsigned y
, unsigned width
,
507 bool selected
, gcc_unused
const void *data
)
509 assert(playlist
!= NULL
);
510 assert(i
< playlist_length(playlist
));
512 const struct mpd_song
*song
= playlist_get(playlist
, i
);
514 struct hscroll
*row_hscroll
= NULL
;
516 row_hscroll
= selected
&& options
.scroll
&& lw
->selected
== i
520 paint_song_row(w
, y
, width
, selected
,
521 (int)mpd_song_get_id(song
) == current_song_id
,
522 song
, row_hscroll
, options
.list_format
);
526 screen_queue_paint(void)
530 hscroll_clear(&hscroll
);
533 list_window_paint2(lw
, screen_queue_paint_callback
, NULL
);
537 screen_queue_update(struct mpdclient
*c
)
539 if (c
->events
& MPD_IDLE_QUEUE
)
540 screen_queue_restore_selection();
542 /* the queue size may have changed, even if we havn't
543 received the QUEUE idle event yet */
544 list_window_set_length(lw
, playlist_length(playlist
));
546 if (((c
->events
& MPD_IDLE_PLAYER
) != 0 &&
547 screen_queue_song_change(c
->status
)) ||
548 c
->events
& MPD_IDLE_QUEUE
)
549 /* the queue or the current song has changed, we must
550 paint the new version */
551 screen_queue_paint();
556 handle_mouse_event(struct mpdclient
*c
)
558 unsigned long bstate
;
560 if (screen_get_mouse_event(c
, &bstate
, &row
) ||
561 list_window_mouse(lw
, bstate
, row
)) {
562 screen_queue_paint();
566 if (bstate
& BUTTON1_DOUBLE_CLICKED
) {
568 screen_cmd(c
, CMD_STOP
);
572 const unsigned old_selected
= lw
->selected
;
573 list_window_set_cursor(lw
, lw
->start
+ row
);
575 if (bstate
& BUTTON1_CLICKED
) {
577 const struct mpd_song
*song
= screen_queue_selected_song();
579 struct mpd_connection
*connection
=
580 mpdclient_get_connection(c
);
582 if (connection
!= NULL
&&
583 !mpd_run_play_id(connection
,
584 mpd_song_get_id(song
)))
585 mpdclient_handle_error(c
);
587 } else if (bstate
& BUTTON3_CLICKED
) {
589 if (lw
->selected
== old_selected
)
590 mpdclient_cmd_delete(c
, lw
->selected
);
592 list_window_set_length(lw
, playlist_length(playlist
));
595 screen_queue_save_selection();
596 screen_queue_paint();
603 screen_queue_cmd(struct mpdclient
*c
, command_t cmd
)
605 struct mpd_connection
*connection
;
606 static command_t cached_cmd
= CMD_NONE
;
608 const command_t prev_cmd
= cached_cmd
;
611 lw
->hide_cursor
= false;
613 if (options
.hide_cursor
> 0) {
614 if (timer_hide_cursor_id
!= 0)
615 g_source_remove(timer_hide_cursor_id
);
616 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
617 timer_hide_cursor
, c
);
620 if (list_window_cmd(lw
, cmd
)) {
621 screen_queue_save_selection();
622 screen_queue_paint();
627 case CMD_SCREEN_UPDATE
:
628 center_playing_item(c
->status
, prev_cmd
== CMD_SCREEN_UPDATE
);
629 screen_queue_paint();
631 case CMD_SELECT_PLAYING
:
632 list_window_set_cursor(lw
, playlist_get_index(&c
->playlist
,
634 screen_queue_save_selection();
635 screen_queue_paint();
640 case CMD_LIST_FIND_NEXT
:
641 case CMD_LIST_RFIND_NEXT
:
642 screen_find(lw
, cmd
, screen_queue_lw_callback
, NULL
);
643 screen_queue_save_selection();
644 screen_queue_paint();
647 screen_jump(lw
, screen_queue_lw_callback
, NULL
, NULL
, NULL
);
648 screen_queue_save_selection();
649 screen_queue_paint();
653 case CMD_MOUSE_EVENT
:
654 return handle_mouse_event(c
);
657 #ifdef ENABLE_SONG_SCREEN
658 case CMD_SCREEN_SONG
:
659 if (screen_queue_selected_song() != NULL
) {
660 screen_song_switch(c
, screen_queue_selected_song());
667 #ifdef ENABLE_LYRICS_SCREEN
668 case CMD_SCREEN_LYRICS
:
669 if (lw
->selected
< playlist_length(&c
->playlist
)) {
670 struct mpd_song
*selected
= playlist_get(&c
->playlist
, lw
->selected
);
673 if (c
->song
&& selected
&&
674 !strcmp(mpd_song_get_uri(selected
),
675 mpd_song_get_uri(c
->song
)))
678 screen_lyrics_switch(c
, selected
, follow
);
684 case CMD_SCREEN_SWAP
:
685 if (playlist_length(&c
->playlist
) > 0)
686 screen_swap(c
, playlist_get(&c
->playlist
, lw
->selected
));
688 screen_swap(c
, NULL
);
695 if (!mpdclient_is_connected(c
))
699 const struct mpd_song
*song
;
700 struct list_window_range range
;
703 song
= screen_queue_selected_song();
707 connection
= mpdclient_get_connection(c
);
708 if (connection
!= NULL
&&
709 !mpd_run_play_id(connection
, mpd_song_get_id(song
)))
710 mpdclient_handle_error(c
);
715 list_window_get_range(lw
, &range
);
716 mpdclient_cmd_delete_range(c
, range
.start
, range
.end
);
718 list_window_set_cursor(lw
, range
.start
);
721 case CMD_SAVE_PLAYLIST
:
722 playlist_save(c
, NULL
, NULL
);
726 handle_add_to_playlist(c
);
730 list_window_get_range(lw
, &range
);
731 if (range
.end
<= range
.start
+ 1)
732 /* No range selection, shuffle all list. */
735 connection
= mpdclient_get_connection(c
);
736 if (connection
== NULL
)
739 if (mpd_run_shuffle_range(connection
, range
.start
, range
.end
))
740 screen_status_message(_("Shuffled queue"));
742 mpdclient_handle_error(c
);
745 case CMD_LIST_MOVE_UP
:
746 list_window_get_range(lw
, &range
);
747 if (range
.start
== 0 || range
.end
<= range
.start
)
750 if (!mpdclient_cmd_move(c
, range
.end
- 1, range
.start
- 1))
756 if (lw
->range_selection
)
757 list_window_scroll_to(lw
, lw
->range_base
);
758 list_window_scroll_to(lw
, lw
->selected
);
760 screen_queue_save_selection();
763 case CMD_LIST_MOVE_DOWN
:
764 list_window_get_range(lw
, &range
);
765 if (range
.end
>= playlist_length(&c
->playlist
))
768 if (!mpdclient_cmd_move(c
, range
.start
, range
.end
))
774 if (lw
->range_selection
)
775 list_window_scroll_to(lw
, lw
->range_base
);
776 list_window_scroll_to(lw
, lw
->selected
);
778 screen_queue_save_selection();
782 if (screen_queue_selected_song() != NULL
) {
783 screen_file_goto_song(c
, screen_queue_selected_song());
796 const struct screen_functions screen_queue
= {
797 .init
= screen_queue_init
,
798 .exit
= screen_queue_exit
,
799 .open
= screen_queue_open
,
800 .close
= screen_queue_close
,
801 .resize
= screen_queue_resize
,
802 .paint
= screen_queue_paint
,
803 .update
= screen_queue_update
,
804 .cmd
= screen_queue_cmd
,
805 .get_title
= screen_queue_title
,