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
60 } completion_callback_data_t
;
62 static struct hscroll hscroll
;
65 static struct mpdclient_playlist
*playlist
;
66 static int current_song_id
= -1;
67 static int selected_song_id
= -1;
68 static struct list_window
*lw
;
69 static guint timer_hide_cursor_id
;
72 screen_queue_paint(void);
75 screen_queue_repaint(void)
81 static const struct mpd_song
*
82 screen_queue_selected_song(void)
84 return !lw
->range_selection
&&
85 lw
->selected
< playlist_length(playlist
)
86 ? playlist_get(playlist
, lw
->selected
)
91 screen_queue_save_selection(void)
93 selected_song_id
= screen_queue_selected_song() != NULL
94 ? (int)mpd_song_get_id(screen_queue_selected_song())
99 screen_queue_restore_selection(void)
101 list_window_set_length(lw
, playlist_length(playlist
));
103 if (selected_song_id
< 0)
104 /* there was no selection */
107 const struct mpd_song
*song
= screen_queue_selected_song();
109 mpd_song_get_id(song
) == (unsigned)selected_song_id
)
110 /* selection is still valid */
113 int pos
= playlist_get_index_from_id(playlist
, selected_song_id
);
115 list_window_set_cursor(lw
, pos
);
117 screen_queue_save_selection();
121 screen_queue_lw_callback(unsigned idx
, gcc_unused
void *data
)
123 static char songname
[MAX_SONG_LENGTH
];
125 assert(playlist
!= NULL
);
126 assert(idx
< playlist_length(playlist
));
128 struct mpd_song
*song
= playlist_get(playlist
, idx
);
130 strfsong(songname
, MAX_SONG_LENGTH
, options
.list_format
, song
);
136 center_playing_item(const struct mpd_status
*status
, bool center_cursor
)
138 if (status
== NULL
||
139 (mpd_status_get_state(status
) != MPD_STATE_PLAY
&&
140 mpd_status_get_state(status
) != MPD_STATE_PAUSE
))
143 /* try to center the song that are playing */
144 int idx
= mpd_status_get_song_pos(status
);
148 list_window_center(lw
, idx
);
151 list_window_set_cursor(lw
, idx
);
155 /* make sure the cursor is in the window */
156 list_window_fetch_cursor(lw
);
161 get_current_song_id(const struct mpd_status
*status
)
163 return status
!= NULL
&&
164 (mpd_status_get_state(status
) == MPD_STATE_PLAY
||
165 mpd_status_get_state(status
) == MPD_STATE_PAUSE
)
166 ? (int)mpd_status_get_song_id(status
)
171 screen_queue_song_change(const struct mpd_status
*status
)
173 if (get_current_song_id(status
) == current_song_id
)
176 current_song_id
= get_current_song_id(status
);
178 /* center the cursor */
179 if (options
.auto_center
&& !lw
->range_selection
)
180 center_playing_item(status
, false);
187 * Wrapper for strncmp(). We are not allowed to pass &strncmp to
188 * g_completion_set_compare(), because strncmp() takes size_t where
189 * g_completion_set_compare passes a gsize value.
192 completion_strncmp(const gchar
*s1
, const gchar
*s2
, gsize n
)
194 return strncmp(s1
, s2
, n
);
199 static void add_dir(GCompletion
*gcmp
, gchar
*dir
, GList
**dir_list
,
200 GList
**list
, struct mpdclient
*c
)
202 g_completion_remove_items(gcmp
, *list
);
203 *list
= string_list_remove(*list
, dir
);
204 *list
= gcmp_list_from_path(c
, dir
, *list
, GCMP_TYPE_RFILE
);
205 g_completion_add_items(gcmp
, *list
);
206 *dir_list
= g_list_append(*dir_list
, g_strdup(dir
));
209 static void add_pre_completion_cb(GCompletion
*gcmp
, gchar
*line
, void *data
)
211 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
212 GList
**dir_list
= tmp
->dir_list
;
213 GList
**list
= tmp
->list
;
214 struct mpdclient
*c
= tmp
->c
;
217 /* create initial list */
218 *list
= gcmp_list_from_path(c
, "", NULL
, GCMP_TYPE_RFILE
);
219 g_completion_add_items(gcmp
, *list
);
220 } else if (line
&& line
[0] && line
[strlen(line
)-1]=='/' &&
221 string_list_find(*dir_list
, line
) == NULL
) {
222 /* add directory content to list */
223 add_dir(gcmp
, line
, dir_list
, list
, c
);
227 static void add_post_completion_cb(GCompletion
*gcmp
, gchar
*line
,
228 GList
*items
, void *data
)
230 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
231 GList
**dir_list
= tmp
->dir_list
;
232 GList
**list
= tmp
->list
;
233 struct mpdclient
*c
= tmp
->c
;
235 if (g_list_length(items
) >= 1)
236 screen_display_completion_list(items
);
238 if (line
&& line
[0] && line
[strlen(line
) - 1] == '/' &&
239 string_list_find(*dir_list
, line
) == NULL
) {
240 /* add directory content to list */
241 add_dir(gcmp
, line
, dir_list
, list
, c
);
247 handle_add_to_playlist(struct mpdclient
*c
)
250 /* initialize completion support */
251 GCompletion
*gcmp
= g_completion_new(NULL
);
252 g_completion_set_compare(gcmp
, completion_strncmp
);
255 GList
*dir_list
= NULL
;
256 completion_callback_data_t data
= {
258 .dir_list
= &dir_list
,
262 wrln_completion_callback_data
= &data
;
263 wrln_pre_completion_callback
= add_pre_completion_cb
;
264 wrln_post_completion_callback
= add_post_completion_cb
;
266 GCompletion
*gcmp
= NULL
;
270 char *path
= screen_readln(_("Add"),
275 /* destroy completion data */
277 wrln_completion_callback_data
= NULL
;
278 wrln_pre_completion_callback
= NULL
;
279 wrln_post_completion_callback
= NULL
;
280 g_completion_free(gcmp
);
281 string_list_free(list
);
282 string_list_free(dir_list
);
285 /* add the path to the playlist */
287 char *path_utf8
= locale_to_utf8(path
);
288 mpdclient_cmd_add_path(c
, path_utf8
);
297 screen_queue_init(WINDOW
*w
, unsigned cols
, unsigned rows
)
299 lw
= list_window_init(w
, cols
, rows
);
303 hscroll_init(&hscroll
, w
, options
.scroll_sep
);
308 timer_hide_cursor(gpointer data
)
310 struct mpdclient
*c
= data
;
312 assert(options
.hide_cursor
> 0);
313 assert(timer_hide_cursor_id
!= 0);
315 timer_hide_cursor_id
= 0;
317 /* hide the cursor when mpd is playing and the user is inactive */
319 if (c
->status
!= NULL
&&
320 mpd_status_get_state(c
->status
) == MPD_STATE_PLAY
) {
321 lw
->hide_cursor
= true;
322 screen_queue_repaint();
324 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
325 timer_hide_cursor
, c
);
331 screen_queue_open(struct mpdclient
*c
)
333 playlist
= &c
->playlist
;
335 assert(timer_hide_cursor_id
== 0);
336 if (options
.hide_cursor
> 0) {
337 lw
->hide_cursor
= false;
338 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
339 timer_hide_cursor
, c
);
342 screen_queue_restore_selection();
343 screen_queue_song_change(c
->status
);
347 screen_queue_close(void)
349 if (timer_hide_cursor_id
!= 0) {
350 g_source_remove(timer_hide_cursor_id
);
351 timer_hide_cursor_id
= 0;
356 hscroll_clear(&hscroll
);
361 screen_queue_resize(unsigned cols
, unsigned rows
)
363 list_window_resize(lw
, cols
, rows
);
368 screen_queue_exit(void)
370 list_window_free(lw
);
374 screen_queue_title(char *str
, size_t size
)
376 if (options
.host
== NULL
)
379 g_snprintf(str
, size
, _("Queue on %s"), options
.host
);
384 screen_queue_paint_callback(WINDOW
*w
, unsigned i
,
385 unsigned y
, unsigned width
,
386 bool selected
, gcc_unused
const void *data
)
388 assert(playlist
!= NULL
);
389 assert(i
< playlist_length(playlist
));
391 const struct mpd_song
*song
= playlist_get(playlist
, i
);
393 struct hscroll
*row_hscroll
= NULL
;
395 row_hscroll
= selected
&& options
.scroll
&& lw
->selected
== i
399 paint_song_row(w
, y
, width
, selected
,
400 (int)mpd_song_get_id(song
) == current_song_id
,
401 song
, row_hscroll
, options
.list_format
);
405 screen_queue_paint(void)
409 hscroll_clear(&hscroll
);
412 list_window_paint2(lw
, screen_queue_paint_callback
, NULL
);
416 screen_queue_update(struct mpdclient
*c
)
418 if (c
->events
& MPD_IDLE_QUEUE
)
419 screen_queue_restore_selection();
421 /* the queue size may have changed, even if we havn't
422 received the QUEUE idle event yet */
423 list_window_set_length(lw
, playlist_length(playlist
));
425 if (((c
->events
& MPD_IDLE_PLAYER
) != 0 &&
426 screen_queue_song_change(c
->status
)) ||
427 c
->events
& MPD_IDLE_QUEUE
)
428 /* the queue or the current song has changed, we must
429 paint the new version */
430 screen_queue_paint();
435 handle_mouse_event(struct mpdclient
*c
)
437 unsigned long bstate
;
439 if (screen_get_mouse_event(c
, &bstate
, &row
) ||
440 list_window_mouse(lw
, bstate
, row
)) {
441 screen_queue_paint();
445 if (bstate
& BUTTON1_DOUBLE_CLICKED
) {
447 screen_cmd(c
, CMD_STOP
);
451 const unsigned old_selected
= lw
->selected
;
452 list_window_set_cursor(lw
, lw
->start
+ row
);
454 if (bstate
& BUTTON1_CLICKED
) {
456 const struct mpd_song
*song
= screen_queue_selected_song();
458 struct mpd_connection
*connection
=
459 mpdclient_get_connection(c
);
461 if (connection
!= NULL
&&
462 !mpd_run_play_id(connection
,
463 mpd_song_get_id(song
)))
464 mpdclient_handle_error(c
);
466 } else if (bstate
& BUTTON3_CLICKED
) {
468 if (lw
->selected
== old_selected
)
469 mpdclient_cmd_delete(c
, lw
->selected
);
471 list_window_set_length(lw
, playlist_length(playlist
));
474 screen_queue_save_selection();
475 screen_queue_paint();
482 screen_queue_cmd(struct mpdclient
*c
, command_t cmd
)
484 struct mpd_connection
*connection
;
485 static command_t cached_cmd
= CMD_NONE
;
487 const command_t prev_cmd
= cached_cmd
;
490 lw
->hide_cursor
= false;
492 if (options
.hide_cursor
> 0) {
493 if (timer_hide_cursor_id
!= 0)
494 g_source_remove(timer_hide_cursor_id
);
495 timer_hide_cursor_id
= g_timeout_add_seconds(options
.hide_cursor
,
496 timer_hide_cursor
, c
);
499 if (list_window_cmd(lw
, cmd
)) {
500 screen_queue_save_selection();
501 screen_queue_paint();
506 case CMD_SCREEN_UPDATE
:
507 center_playing_item(c
->status
, prev_cmd
== CMD_SCREEN_UPDATE
);
508 screen_queue_paint();
510 case CMD_SELECT_PLAYING
:
511 list_window_set_cursor(lw
, playlist_get_index(&c
->playlist
,
513 screen_queue_save_selection();
514 screen_queue_paint();
519 case CMD_LIST_FIND_NEXT
:
520 case CMD_LIST_RFIND_NEXT
:
521 screen_find(lw
, cmd
, screen_queue_lw_callback
, NULL
);
522 screen_queue_save_selection();
523 screen_queue_paint();
526 screen_jump(lw
, screen_queue_lw_callback
, NULL
, NULL
, NULL
);
527 screen_queue_save_selection();
528 screen_queue_paint();
532 case CMD_MOUSE_EVENT
:
533 return handle_mouse_event(c
);
536 #ifdef ENABLE_SONG_SCREEN
537 case CMD_SCREEN_SONG
:
538 if (screen_queue_selected_song() != NULL
) {
539 screen_song_switch(c
, screen_queue_selected_song());
546 #ifdef ENABLE_LYRICS_SCREEN
547 case CMD_SCREEN_LYRICS
:
548 if (lw
->selected
< playlist_length(&c
->playlist
)) {
549 struct mpd_song
*selected
= playlist_get(&c
->playlist
, lw
->selected
);
552 if (c
->song
&& selected
&&
553 !strcmp(mpd_song_get_uri(selected
),
554 mpd_song_get_uri(c
->song
)))
557 screen_lyrics_switch(c
, selected
, follow
);
563 case CMD_SCREEN_SWAP
:
564 if (playlist_length(&c
->playlist
) > 0)
565 screen_swap(c
, playlist_get(&c
->playlist
, lw
->selected
));
567 screen_swap(c
, NULL
);
574 if (!mpdclient_is_connected(c
))
578 const struct mpd_song
*song
;
579 struct list_window_range range
;
582 song
= screen_queue_selected_song();
586 connection
= mpdclient_get_connection(c
);
587 if (connection
!= NULL
&&
588 !mpd_run_play_id(connection
, mpd_song_get_id(song
)))
589 mpdclient_handle_error(c
);
594 list_window_get_range(lw
, &range
);
595 mpdclient_cmd_delete_range(c
, range
.start
, range
.end
);
597 list_window_set_cursor(lw
, range
.start
);
600 case CMD_SAVE_PLAYLIST
:
601 playlist_save(c
, NULL
, NULL
);
605 handle_add_to_playlist(c
);
609 list_window_get_range(lw
, &range
);
610 if (range
.end
<= range
.start
+ 1)
611 /* No range selection, shuffle all list. */
614 connection
= mpdclient_get_connection(c
);
615 if (connection
== NULL
)
618 if (mpd_run_shuffle_range(connection
, range
.start
, range
.end
))
619 screen_status_message(_("Shuffled queue"));
621 mpdclient_handle_error(c
);
624 case CMD_LIST_MOVE_UP
:
625 list_window_get_range(lw
, &range
);
626 if (range
.start
== 0 || range
.end
<= range
.start
)
629 if (!mpdclient_cmd_move(c
, range
.end
- 1, range
.start
- 1))
635 if (lw
->range_selection
)
636 list_window_scroll_to(lw
, lw
->range_base
);
637 list_window_scroll_to(lw
, lw
->selected
);
639 screen_queue_save_selection();
642 case CMD_LIST_MOVE_DOWN
:
643 list_window_get_range(lw
, &range
);
644 if (range
.end
>= playlist_length(&c
->playlist
))
647 if (!mpdclient_cmd_move(c
, range
.start
, range
.end
))
653 if (lw
->range_selection
)
654 list_window_scroll_to(lw
, lw
->range_base
);
655 list_window_scroll_to(lw
, lw
->selected
);
657 screen_queue_save_selection();
661 if (screen_queue_selected_song() != NULL
) {
662 screen_file_goto_song(c
, screen_queue_selected_song());
675 const struct screen_functions screen_queue
= {
676 .init
= screen_queue_init
,
677 .exit
= screen_queue_exit
,
678 .open
= screen_queue_open
,
679 .close
= screen_queue_close
,
680 .resize
= screen_queue_resize
,
681 .paint
= screen_queue_paint
,
682 .update
= screen_queue_update
,
683 .cmd
= screen_queue_cmd
,
684 .get_title
= screen_queue_title
,