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"
25 #include "save_playlist.h"
30 #include "mpdclient.h"
34 #include "song_paint.h"
36 #include "screen_utils.h"
37 #include "screen_song.h"
38 #include "screen_lyrics.h"
39 #include "db_completion.h"
46 #include <mpd/client.h>
52 #define MAX_SONG_LENGTH 512
55 static struct hscroll hscroll
;
58 static struct mpdclient_playlist
*playlist
;
59 static int current_song_id
= -1;
60 static int selected_song_id
= -1;
61 static struct list_window
*lw
;
62 static guint timer_hide_cursor_id
;
64 static struct queue_screen_data
{
65 unsigned last_connection_id
;
66 char *connection_name
;
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 * Wrapper for strncmp(). We are not allowed to pass &strncmp to
186 * g_completion_set_compare(), because strncmp() takes size_t where
187 * g_completion_set_compare passes a gsize value.
190 completion_strncmp(const gchar
*s1
, const gchar
*s2
, gsize n
)
192 return strncmp(s1
, s2
, n
);
197 static void add_dir(GCompletion
*gcmp
, gchar
*dir
, GList
**dir_list
,
198 GList
**list
, struct mpdclient
*c
)
200 g_completion_remove_items(gcmp
, *list
);
201 *list
= string_list_remove(*list
, dir
);
202 *list
= gcmp_list_from_path(c
, dir
, *list
, GCMP_TYPE_RFILE
);
203 g_completion_add_items(gcmp
, *list
);
204 *dir_list
= g_list_append(*dir_list
, g_strdup(dir
));
207 struct completion_callback_data
{
213 static void add_pre_completion_cb(GCompletion
*gcmp
, gchar
*line
, void *data
)
215 struct completion_callback_data
*tmp
= data
;
216 GList
**dir_list
= tmp
->dir_list
;
217 GList
**list
= tmp
->list
;
218 struct mpdclient
*c
= tmp
->c
;
221 /* create initial list */
222 *list
= gcmp_list_from_path(c
, "", NULL
, GCMP_TYPE_RFILE
);
223 g_completion_add_items(gcmp
, *list
);
224 } else if (line
&& line
[0] && line
[strlen(line
)-1]=='/' &&
225 string_list_find(*dir_list
, line
) == NULL
) {
226 /* add directory content to list */
227 add_dir(gcmp
, line
, dir_list
, list
, c
);
231 static void add_post_completion_cb(GCompletion
*gcmp
, gchar
*line
,
232 GList
*items
, void *data
)
234 struct completion_callback_data
*tmp
= data
;
235 GList
**dir_list
= tmp
->dir_list
;
236 GList
**list
= tmp
->list
;
237 struct mpdclient
*c
= tmp
->c
;
239 if (g_list_length(items
) >= 1)
240 screen_display_completion_list(items
);
242 if (line
&& line
[0] && line
[strlen(line
) - 1] == '/' &&
243 string_list_find(*dir_list
, line
) == NULL
) {
244 /* add directory content to list */
245 add_dir(gcmp
, line
, dir_list
, list
, c
);
251 handle_add_to_playlist(struct mpdclient
*c
)
254 /* initialize completion support */
255 GCompletion
*gcmp
= g_completion_new(NULL
);
256 g_completion_set_compare(gcmp
, completion_strncmp
);
259 GList
*dir_list
= NULL
;
260 struct completion_callback_data data
= {
262 .dir_list
= &dir_list
,
266 wrln_completion_callback_data
= &data
;
267 wrln_pre_completion_callback
= add_pre_completion_cb
;
268 wrln_post_completion_callback
= add_post_completion_cb
;
270 GCompletion
*gcmp
= NULL
;
274 char *path
= screen_readln(_("Add"),
279 /* destroy completion data */
281 wrln_completion_callback_data
= NULL
;
282 wrln_pre_completion_callback
= NULL
;
283 wrln_post_completion_callback
= NULL
;
284 g_completion_free(gcmp
);
285 string_list_free(list
);
286 string_list_free(dir_list
);
289 /* add the path to the playlist */
291 char *path_utf8
= locale_to_utf8(path
);
292 mpdclient_cmd_add_path(c
, path_utf8
);
301 screen_queue_init(WINDOW
*w
, unsigned cols
, unsigned rows
)
303 lw
= list_window_init(w
, cols
, rows
);
307 hscroll_init(&hscroll
, w
, options
.scroll_sep
);
312 timer_hide_cursor(gpointer data
)
314 struct mpdclient
*c
= data
;
316 assert(options
.hide_cursor
> 0);
317 assert(timer_hide_cursor_id
!= 0);
319 timer_hide_cursor_id
= 0;
321 /* hide the cursor when mpd is playing and the user is inactive */
323 if (c
->status
!= NULL
&&
324 mpd_status_get_state(c
->status
) == MPD_STATE_PLAY
) {
325 lw
->hide_cursor
= true;
326 screen_queue_repaint();
328 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
329 timer_hide_cursor
, c
);
335 screen_queue_open(struct mpdclient
*c
)
337 playlist
= &c
->playlist
;
339 assert(timer_hide_cursor_id
== 0);
340 if (options
.hide_cursor
> 0) {
341 lw
->hide_cursor
= false;
342 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
343 timer_hide_cursor
, c
);
346 screen_queue_restore_selection();
347 screen_queue_song_change(c
->status
);
351 screen_queue_close(void)
353 if (timer_hide_cursor_id
!= 0) {
354 g_source_remove(timer_hide_cursor_id
);
355 timer_hide_cursor_id
= 0;
360 hscroll_clear(&hscroll
);
365 screen_queue_resize(unsigned cols
, unsigned rows
)
367 list_window_resize(lw
, cols
, rows
);
372 screen_queue_exit(void)
374 list_window_free(lw
);
375 g_free(queue_screen
.connection_name
);
379 screen_queue_title(char *str
, size_t size
)
381 if (queue_screen
.connection_name
== NULL
)
384 g_snprintf(str
, size
, _("Queue on %s"), queue_screen
.connection_name
);
389 screen_queue_paint_callback(WINDOW
*w
, unsigned i
,
390 unsigned y
, unsigned width
,
391 bool selected
, gcc_unused
const void *data
)
393 assert(playlist
!= NULL
);
394 assert(i
< playlist_length(playlist
));
396 const struct mpd_song
*song
= playlist_get(playlist
, i
);
398 struct hscroll
*row_hscroll
= NULL
;
400 row_hscroll
= selected
&& options
.scroll
&& lw
->selected
== i
404 paint_song_row(w
, y
, width
, selected
,
405 (int)mpd_song_get_id(song
) == current_song_id
,
406 song
, row_hscroll
, options
.list_format
);
410 screen_queue_paint(void)
414 hscroll_clear(&hscroll
);
417 list_window_paint2(lw
, screen_queue_paint_callback
, NULL
);
421 screen_queue_update(struct mpdclient
*c
)
423 if (c
->connection_id
!= queue_screen
.last_connection_id
) {
424 queue_screen
.last_connection_id
= c
->connection_id
;
425 g_free(queue_screen
.connection_name
);
426 queue_screen
.connection_name
= mpdclient_settings_name(c
);
429 if (c
->events
& MPD_IDLE_QUEUE
)
430 screen_queue_restore_selection();
432 /* the queue size may have changed, even if we havn't
433 received the QUEUE idle event yet */
434 list_window_set_length(lw
, playlist_length(playlist
));
436 if (((c
->events
& MPD_IDLE_PLAYER
) != 0 &&
437 screen_queue_song_change(c
->status
)) ||
438 c
->events
& MPD_IDLE_QUEUE
)
439 /* the queue or the current song has changed, we must
440 paint the new version */
441 screen_queue_paint();
446 screen_queue_mouse(struct mpdclient
*c
, gcc_unused
int x
, int row
,
449 if (list_window_mouse(lw
, bstate
, row
)) {
450 screen_queue_paint();
454 if (bstate
& BUTTON1_DOUBLE_CLICKED
) {
456 screen_cmd(c
, CMD_STOP
);
460 const unsigned old_selected
= lw
->selected
;
461 list_window_set_cursor(lw
, lw
->start
+ row
);
463 if (bstate
& BUTTON1_CLICKED
) {
465 const struct mpd_song
*song
= screen_queue_selected_song();
467 struct mpd_connection
*connection
=
468 mpdclient_get_connection(c
);
470 if (connection
!= NULL
&&
471 !mpd_run_play_id(connection
,
472 mpd_song_get_id(song
)))
473 mpdclient_handle_error(c
);
475 } else if (bstate
& BUTTON3_CLICKED
) {
477 if (lw
->selected
== old_selected
)
478 mpdclient_cmd_delete(c
, lw
->selected
);
480 list_window_set_length(lw
, playlist_length(playlist
));
483 screen_queue_save_selection();
484 screen_queue_paint();
491 screen_queue_cmd(struct mpdclient
*c
, command_t cmd
)
493 struct mpd_connection
*connection
;
494 static command_t cached_cmd
= CMD_NONE
;
496 const command_t prev_cmd
= cached_cmd
;
499 lw
->hide_cursor
= false;
501 if (options
.hide_cursor
> 0) {
502 if (timer_hide_cursor_id
!= 0)
503 g_source_remove(timer_hide_cursor_id
);
504 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
505 timer_hide_cursor
, c
);
508 if (list_window_cmd(lw
, cmd
)) {
509 screen_queue_save_selection();
510 screen_queue_paint();
515 case CMD_SCREEN_UPDATE
:
516 center_playing_item(c
->status
, prev_cmd
== CMD_SCREEN_UPDATE
);
517 screen_queue_paint();
519 case CMD_SELECT_PLAYING
:
520 list_window_set_cursor(lw
, playlist_get_index(&c
->playlist
,
522 screen_queue_save_selection();
523 screen_queue_paint();
528 case CMD_LIST_FIND_NEXT
:
529 case CMD_LIST_RFIND_NEXT
:
530 screen_find(lw
, cmd
, screen_queue_lw_callback
, NULL
);
531 screen_queue_save_selection();
532 screen_queue_paint();
535 screen_jump(lw
, screen_queue_lw_callback
, NULL
, NULL
, NULL
);
536 screen_queue_save_selection();
537 screen_queue_paint();
540 #ifdef ENABLE_SONG_SCREEN
541 case CMD_SCREEN_SONG
:
542 if (screen_queue_selected_song() != NULL
) {
543 screen_song_switch(c
, screen_queue_selected_song());
550 #ifdef ENABLE_LYRICS_SCREEN
551 case CMD_SCREEN_LYRICS
:
552 if (lw
->selected
< playlist_length(&c
->playlist
)) {
553 struct mpd_song
*selected
= playlist_get(&c
->playlist
, lw
->selected
);
556 if (c
->song
&& selected
&&
557 !strcmp(mpd_song_get_uri(selected
),
558 mpd_song_get_uri(c
->song
)))
561 screen_lyrics_switch(c
, selected
, follow
);
567 case CMD_SCREEN_SWAP
:
568 if (playlist_length(&c
->playlist
) > 0)
569 screen_swap(c
, playlist_get(&c
->playlist
, lw
->selected
));
571 screen_swap(c
, NULL
);
578 if (!mpdclient_is_connected(c
))
582 const struct mpd_song
*song
;
583 struct list_window_range range
;
586 song
= screen_queue_selected_song();
590 connection
= mpdclient_get_connection(c
);
591 if (connection
!= NULL
&&
592 !mpd_run_play_id(connection
, mpd_song_get_id(song
)))
593 mpdclient_handle_error(c
);
598 list_window_get_range(lw
, &range
);
599 mpdclient_cmd_delete_range(c
, range
.start
, range
.end
);
601 list_window_set_cursor(lw
, range
.start
);
604 case CMD_SAVE_PLAYLIST
:
605 playlist_save(c
, NULL
, NULL
);
609 handle_add_to_playlist(c
);
613 list_window_get_range(lw
, &range
);
614 if (range
.end
<= range
.start
+ 1)
615 /* No range selection, shuffle all list. */
618 connection
= mpdclient_get_connection(c
);
619 if (connection
== NULL
)
622 if (mpd_run_shuffle_range(connection
, range
.start
, range
.end
))
623 screen_status_message(_("Shuffled queue"));
625 mpdclient_handle_error(c
);
628 case CMD_LIST_MOVE_UP
:
629 list_window_get_range(lw
, &range
);
630 if (range
.start
== 0 || range
.end
<= range
.start
)
633 if (!mpdclient_cmd_move(c
, range
.end
- 1, range
.start
- 1))
639 if (lw
->range_selection
)
640 list_window_scroll_to(lw
, lw
->range_base
);
641 list_window_scroll_to(lw
, lw
->selected
);
643 screen_queue_save_selection();
646 case CMD_LIST_MOVE_DOWN
:
647 list_window_get_range(lw
, &range
);
648 if (range
.end
>= playlist_length(&c
->playlist
))
651 if (!mpdclient_cmd_move(c
, range
.start
, range
.end
))
657 if (lw
->range_selection
)
658 list_window_scroll_to(lw
, lw
->range_base
);
659 list_window_scroll_to(lw
, lw
->selected
);
661 screen_queue_save_selection();
665 if (screen_queue_selected_song() != NULL
) {
666 screen_file_goto_song(c
, screen_queue_selected_song());
679 const struct screen_functions screen_queue
= {
680 .init
= screen_queue_init
,
681 .exit
= screen_queue_exit
,
682 .open
= screen_queue_open
,
683 .close
= screen_queue_close
,
684 .resize
= screen_queue_resize
,
685 .paint
= screen_queue_paint
,
686 .update
= screen_queue_update
,
687 .cmd
= screen_queue_cmd
,
689 .mouse
= screen_queue_mouse
,
691 .get_title
= screen_queue_title
,