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"
45 #include <mpd/client.h>
51 #define MAX_SONG_LENGTH 512
59 } completion_callback_data_t
;
61 static struct hscroll hscroll
;
64 static struct mpdclient_playlist
*playlist
;
65 static int current_song_id
= -1;
66 static int selected_song_id
= -1;
67 static struct list_window
*lw
;
68 static guint timer_hide_cursor_id
;
71 screen_queue_paint(void);
74 screen_queue_repaint(void)
80 static const struct mpd_song
*
81 screen_queue_selected_song(void)
83 return !lw
->range_selection
&&
84 lw
->selected
< playlist_length(playlist
)
85 ? playlist_get(playlist
, lw
->selected
)
90 screen_queue_save_selection(void)
92 selected_song_id
= screen_queue_selected_song() != NULL
93 ? (int)mpd_song_get_id(screen_queue_selected_song())
98 screen_queue_restore_selection(void)
100 list_window_set_length(lw
, playlist_length(playlist
));
102 if (selected_song_id
< 0)
103 /* there was no selection */
106 const struct mpd_song
*song
= screen_queue_selected_song();
108 mpd_song_get_id(song
) == (unsigned)selected_song_id
)
109 /* selection is still valid */
112 int pos
= playlist_get_index_from_id(playlist
, selected_song_id
);
114 list_window_set_cursor(lw
, pos
);
116 screen_queue_save_selection();
120 screen_queue_lw_callback(unsigned idx
, gcc_unused
void *data
)
122 static char songname
[MAX_SONG_LENGTH
];
124 assert(playlist
!= NULL
);
125 assert(idx
< playlist_length(playlist
));
127 struct mpd_song
*song
= playlist_get(playlist
, idx
);
129 strfsong(songname
, MAX_SONG_LENGTH
, options
.list_format
, song
);
135 center_playing_item(const struct mpd_status
*status
, bool center_cursor
)
137 if (status
== NULL
||
138 (mpd_status_get_state(status
) != MPD_STATE_PLAY
&&
139 mpd_status_get_state(status
) != MPD_STATE_PAUSE
))
142 /* try to center the song that are playing */
143 int idx
= mpd_status_get_song_pos(status
);
147 list_window_center(lw
, idx
);
150 list_window_set_cursor(lw
, idx
);
154 /* make sure the cursor is in the window */
155 list_window_fetch_cursor(lw
);
160 get_current_song_id(const struct mpd_status
*status
)
162 return status
!= NULL
&&
163 (mpd_status_get_state(status
) == MPD_STATE_PLAY
||
164 mpd_status_get_state(status
) == MPD_STATE_PAUSE
)
165 ? (int)mpd_status_get_song_id(status
)
170 screen_queue_song_change(const struct mpd_status
*status
)
172 if (get_current_song_id(status
) == current_song_id
)
175 current_song_id
= get_current_song_id(status
);
177 /* center the cursor */
178 if (options
.auto_center
&& !lw
->range_selection
)
179 center_playing_item(status
, false);
186 * Wrapper for strncmp(). We are not allowed to pass &strncmp to
187 * g_completion_set_compare(), because strncmp() takes size_t where
188 * g_completion_set_compare passes a gsize value.
191 completion_strncmp(const gchar
*s1
, const gchar
*s2
, gsize n
)
193 return strncmp(s1
, s2
, n
);
198 static void add_dir(GCompletion
*gcmp
, gchar
*dir
, GList
**dir_list
,
199 GList
**list
, struct mpdclient
*c
)
201 g_completion_remove_items(gcmp
, *list
);
202 *list
= string_list_remove(*list
, dir
);
203 *list
= gcmp_list_from_path(c
, dir
, *list
, GCMP_TYPE_RFILE
);
204 g_completion_add_items(gcmp
, *list
);
205 *dir_list
= g_list_append(*dir_list
, g_strdup(dir
));
208 static void add_pre_completion_cb(GCompletion
*gcmp
, gchar
*line
, void *data
)
210 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
211 GList
**dir_list
= tmp
->dir_list
;
212 GList
**list
= tmp
->list
;
213 struct mpdclient
*c
= tmp
->c
;
216 /* create initial list */
217 *list
= gcmp_list_from_path(c
, "", NULL
, GCMP_TYPE_RFILE
);
218 g_completion_add_items(gcmp
, *list
);
219 } else if (line
&& line
[0] && line
[strlen(line
)-1]=='/' &&
220 string_list_find(*dir_list
, line
) == NULL
) {
221 /* add directory content to list */
222 add_dir(gcmp
, line
, dir_list
, list
, c
);
226 static void add_post_completion_cb(GCompletion
*gcmp
, gchar
*line
,
227 GList
*items
, void *data
)
229 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
230 GList
**dir_list
= tmp
->dir_list
;
231 GList
**list
= tmp
->list
;
232 struct mpdclient
*c
= tmp
->c
;
234 if (g_list_length(items
) >= 1)
235 screen_display_completion_list(items
);
237 if (line
&& line
[0] && line
[strlen(line
) - 1] == '/' &&
238 string_list_find(*dir_list
, line
) == NULL
) {
239 /* add directory content to list */
240 add_dir(gcmp
, line
, dir_list
, list
, c
);
246 handle_add_to_playlist(struct mpdclient
*c
)
249 /* initialize completion support */
250 GCompletion
*gcmp
= g_completion_new(NULL
);
251 g_completion_set_compare(gcmp
, completion_strncmp
);
254 GList
*dir_list
= NULL
;
255 completion_callback_data_t data
= {
257 .dir_list
= &dir_list
,
261 wrln_completion_callback_data
= &data
;
262 wrln_pre_completion_callback
= add_pre_completion_cb
;
263 wrln_post_completion_callback
= add_post_completion_cb
;
265 GCompletion
*gcmp
= NULL
;
269 char *path
= screen_readln(_("Add"),
274 /* destroy completion data */
276 wrln_completion_callback_data
= NULL
;
277 wrln_pre_completion_callback
= NULL
;
278 wrln_post_completion_callback
= NULL
;
279 g_completion_free(gcmp
);
280 string_list_free(list
);
281 string_list_free(dir_list
);
284 /* add the path to the playlist */
286 char *path_utf8
= locale_to_utf8(path
);
287 mpdclient_cmd_add_path(c
, path_utf8
);
296 screen_queue_init(WINDOW
*w
, unsigned cols
, unsigned rows
)
298 lw
= list_window_init(w
, cols
, rows
);
302 hscroll_init(&hscroll
, w
, options
.scroll_sep
);
307 timer_hide_cursor(gpointer data
)
309 struct mpdclient
*c
= data
;
311 assert(options
.hide_cursor
> 0);
312 assert(timer_hide_cursor_id
!= 0);
314 timer_hide_cursor_id
= 0;
316 /* hide the cursor when mpd is playing and the user is inactive */
318 if (c
->status
!= NULL
&&
319 mpd_status_get_state(c
->status
) == MPD_STATE_PLAY
) {
320 lw
->hide_cursor
= true;
321 screen_queue_repaint();
323 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
324 timer_hide_cursor
, c
);
330 screen_queue_open(struct mpdclient
*c
)
332 playlist
= &c
->playlist
;
334 assert(timer_hide_cursor_id
== 0);
335 if (options
.hide_cursor
> 0) {
336 lw
->hide_cursor
= false;
337 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
338 timer_hide_cursor
, c
);
341 screen_queue_restore_selection();
342 screen_queue_song_change(c
->status
);
346 screen_queue_close(void)
348 if (timer_hide_cursor_id
!= 0) {
349 g_source_remove(timer_hide_cursor_id
);
350 timer_hide_cursor_id
= 0;
355 hscroll_clear(&hscroll
);
360 screen_queue_resize(unsigned cols
, unsigned rows
)
362 list_window_resize(lw
, cols
, rows
);
367 screen_queue_exit(void)
369 list_window_free(lw
);
373 screen_queue_title(char *str
, size_t size
)
375 if (options
.host
== NULL
)
378 g_snprintf(str
, size
, _("Queue on %s"), options
.host
);
383 screen_queue_paint_callback(WINDOW
*w
, unsigned i
,
384 unsigned y
, unsigned width
,
385 bool selected
, gcc_unused
const void *data
)
387 assert(playlist
!= NULL
);
388 assert(i
< playlist_length(playlist
));
390 const struct mpd_song
*song
= playlist_get(playlist
, i
);
392 struct hscroll
*row_hscroll
= NULL
;
394 row_hscroll
= selected
&& options
.scroll
&& lw
->selected
== i
398 paint_song_row(w
, y
, width
, selected
,
399 (int)mpd_song_get_id(song
) == current_song_id
,
400 song
, row_hscroll
, options
.list_format
);
404 screen_queue_paint(void)
408 hscroll_clear(&hscroll
);
411 list_window_paint2(lw
, screen_queue_paint_callback
, NULL
);
415 screen_queue_update(struct mpdclient
*c
)
417 if (c
->events
& MPD_IDLE_QUEUE
)
418 screen_queue_restore_selection();
420 /* the queue size may have changed, even if we havn't
421 received the QUEUE idle event yet */
422 list_window_set_length(lw
, playlist_length(playlist
));
424 if (((c
->events
& MPD_IDLE_PLAYER
) != 0 &&
425 screen_queue_song_change(c
->status
)) ||
426 c
->events
& MPD_IDLE_QUEUE
)
427 /* the queue or the current song has changed, we must
428 paint the new version */
429 screen_queue_paint();
434 handle_mouse_event(struct mpdclient
*c
)
436 unsigned long bstate
;
438 if (screen_get_mouse_event(c
, &bstate
, &row
) ||
439 list_window_mouse(lw
, bstate
, row
)) {
440 screen_queue_paint();
444 if (bstate
& BUTTON1_DOUBLE_CLICKED
) {
446 screen_cmd(c
, CMD_STOP
);
450 const unsigned old_selected
= lw
->selected
;
451 list_window_set_cursor(lw
, lw
->start
+ row
);
453 if (bstate
& BUTTON1_CLICKED
) {
455 const struct mpd_song
*song
= screen_queue_selected_song();
457 struct mpd_connection
*connection
=
458 mpdclient_get_connection(c
);
460 if (connection
!= NULL
&&
461 !mpd_run_play_id(connection
,
462 mpd_song_get_id(song
)))
463 mpdclient_handle_error(c
);
465 } else if (bstate
& BUTTON3_CLICKED
) {
467 if (lw
->selected
== old_selected
)
468 mpdclient_cmd_delete(c
, lw
->selected
);
470 list_window_set_length(lw
, playlist_length(playlist
));
473 screen_queue_save_selection();
474 screen_queue_paint();
481 screen_queue_cmd(struct mpdclient
*c
, command_t cmd
)
483 struct mpd_connection
*connection
;
484 static command_t cached_cmd
= CMD_NONE
;
486 const command_t prev_cmd
= cached_cmd
;
489 lw
->hide_cursor
= false;
491 if (options
.hide_cursor
> 0) {
492 if (timer_hide_cursor_id
!= 0)
493 g_source_remove(timer_hide_cursor_id
);
494 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
495 timer_hide_cursor
, c
);
498 if (list_window_cmd(lw
, cmd
)) {
499 screen_queue_save_selection();
500 screen_queue_paint();
505 case CMD_SCREEN_UPDATE
:
506 center_playing_item(c
->status
, prev_cmd
== CMD_SCREEN_UPDATE
);
507 screen_queue_paint();
509 case CMD_SELECT_PLAYING
:
510 list_window_set_cursor(lw
, playlist_get_index(&c
->playlist
,
512 screen_queue_save_selection();
513 screen_queue_paint();
518 case CMD_LIST_FIND_NEXT
:
519 case CMD_LIST_RFIND_NEXT
:
520 screen_find(lw
, cmd
, screen_queue_lw_callback
, NULL
);
521 screen_queue_save_selection();
522 screen_queue_paint();
525 screen_jump(lw
, screen_queue_lw_callback
, NULL
, NULL
, NULL
);
526 screen_queue_save_selection();
527 screen_queue_paint();
531 case CMD_MOUSE_EVENT
:
532 return handle_mouse_event(c
);
535 #ifdef ENABLE_SONG_SCREEN
536 case CMD_SCREEN_SONG
:
537 if (screen_queue_selected_song() != NULL
) {
538 screen_song_switch(c
, screen_queue_selected_song());
545 #ifdef ENABLE_LYRICS_SCREEN
546 case CMD_SCREEN_LYRICS
:
547 if (lw
->selected
< playlist_length(&c
->playlist
)) {
548 struct mpd_song
*selected
= playlist_get(&c
->playlist
, lw
->selected
);
551 if (c
->song
&& selected
&&
552 !strcmp(mpd_song_get_uri(selected
),
553 mpd_song_get_uri(c
->song
)))
556 screen_lyrics_switch(c
, selected
, follow
);
562 case CMD_SCREEN_SWAP
:
563 if (playlist_length(&c
->playlist
) > 0)
564 screen_swap(c
, playlist_get(&c
->playlist
, lw
->selected
));
566 screen_swap(c
, NULL
);
573 if (!mpdclient_is_connected(c
))
577 const struct mpd_song
*song
;
578 struct list_window_range range
;
581 song
= screen_queue_selected_song();
585 connection
= mpdclient_get_connection(c
);
586 if (connection
!= NULL
&&
587 !mpd_run_play_id(connection
, mpd_song_get_id(song
)))
588 mpdclient_handle_error(c
);
593 list_window_get_range(lw
, &range
);
594 mpdclient_cmd_delete_range(c
, range
.start
, range
.end
);
596 list_window_set_cursor(lw
, range
.start
);
599 case CMD_SAVE_PLAYLIST
:
600 playlist_save(c
, NULL
, NULL
);
604 handle_add_to_playlist(c
);
608 list_window_get_range(lw
, &range
);
609 if (range
.end
<= range
.start
+ 1)
610 /* No range selection, shuffle all list. */
613 connection
= mpdclient_get_connection(c
);
614 if (connection
== NULL
)
617 if (mpd_run_shuffle_range(connection
, range
.start
, range
.end
))
618 screen_status_message(_("Shuffled queue"));
620 mpdclient_handle_error(c
);
623 case CMD_LIST_MOVE_UP
:
624 list_window_get_range(lw
, &range
);
625 if (range
.start
== 0 || range
.end
<= range
.start
)
628 if (!mpdclient_cmd_move(c
, range
.end
- 1, range
.start
- 1))
634 if (lw
->range_selection
)
635 list_window_scroll_to(lw
, lw
->range_base
);
636 list_window_scroll_to(lw
, lw
->selected
);
638 screen_queue_save_selection();
641 case CMD_LIST_MOVE_DOWN
:
642 list_window_get_range(lw
, &range
);
643 if (range
.end
>= playlist_length(&c
->playlist
))
646 if (!mpdclient_cmd_move(c
, range
.start
, range
.end
))
652 if (lw
->range_selection
)
653 list_window_scroll_to(lw
, lw
->range_base
);
654 list_window_scroll_to(lw
, lw
->selected
);
656 screen_queue_save_selection();
660 if (screen_queue_selected_song() != NULL
) {
661 screen_file_goto_song(c
, screen_queue_selected_song());
674 const struct screen_functions screen_queue
= {
675 .init
= screen_queue_init
,
676 .exit
= screen_queue_exit
,
677 .open
= screen_queue_open
,
678 .close
= screen_queue_close
,
679 .resize
= screen_queue_resize
,
680 .paint
= screen_queue_paint
,
681 .update
= screen_queue_update
,
682 .cmd
= screen_queue_cmd
,
683 .get_title
= screen_queue_title
,