1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2010 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_message.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"
43 #include <mpd/client.h>
49 #define MAX_SONG_LENGTH 512
57 } completion_callback_data_t
;
59 static struct hscroll hscroll
;
62 static struct mpdclient_playlist
*playlist
;
63 static int current_song_id
= -1;
64 static int selected_song_id
= -1;
65 static struct list_window
*lw
;
66 static guint timer_hide_cursor_id
;
69 screen_queue_paint(void);
72 screen_queue_repaint(void)
78 static const struct mpd_song
*
79 screen_queue_selected_song(void)
81 return !lw
->range_selection
&&
82 lw
->selected
< playlist_length(playlist
)
83 ? playlist_get(playlist
, lw
->selected
)
88 screen_queue_save_selection(void)
90 selected_song_id
= screen_queue_selected_song() != NULL
91 ? (int)mpd_song_get_id(screen_queue_selected_song())
96 screen_queue_restore_selection(void)
98 const struct mpd_song
*song
;
101 list_window_set_length(lw
, playlist_length(playlist
));
103 if (selected_song_id
< 0)
104 /* there was no selection */
107 song
= screen_queue_selected_song();
109 mpd_song_get_id(song
) == (unsigned)selected_song_id
)
110 /* selection is still valid */
113 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
, G_GNUC_UNUSED
void *data
)
123 static char songname
[MAX_SONG_LENGTH
];
124 struct mpd_song
*song
;
126 assert(playlist
!= NULL
);
127 assert(idx
< playlist_length(playlist
));
129 song
= playlist_get(playlist
, idx
);
131 strfsong(songname
, MAX_SONG_LENGTH
, options
.list_format
, song
);
137 center_playing_item(const struct mpd_status
*status
, bool center_cursor
)
141 if (status
== NULL
||
142 (mpd_status_get_state(status
) != MPD_STATE_PLAY
&&
143 mpd_status_get_state(status
) != MPD_STATE_PAUSE
))
146 /* try to center the song that are playing */
147 idx
= mpd_status_get_song_pos(status
);
151 list_window_center(lw
, idx
);
154 list_window_set_cursor(lw
, idx
);
158 /* make sure the cursor is in the window */
159 list_window_fetch_cursor(lw
);
164 get_current_song_id(const struct mpd_status
*status
)
166 return status
!= NULL
&&
167 (mpd_status_get_state(status
) == MPD_STATE_PLAY
||
168 mpd_status_get_state(status
) == MPD_STATE_PAUSE
)
169 ? (int)mpd_status_get_song_id(status
)
174 screen_queue_song_change(const struct mpd_status
*status
)
176 if (get_current_song_id(status
) == current_song_id
)
179 current_song_id
= get_current_song_id(status
);
181 /* center the cursor */
182 if (options
.auto_center
&& !lw
->range_selection
)
183 center_playing_item(status
, false);
190 save_pre_completion_cb(GCompletion
*gcmp
, G_GNUC_UNUSED gchar
*line
,
193 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
194 GList
**list
= tmp
->list
;
195 struct mpdclient
*c
= tmp
->c
;
197 if( *list
== NULL
) {
198 /* create completion list */
199 *list
= gcmp_list_from_path(c
, "", NULL
, GCMP_TYPE_PLAYLIST
);
200 g_completion_add_items(gcmp
, *list
);
205 save_post_completion_cb(G_GNUC_UNUSED GCompletion
*gcmp
,
206 G_GNUC_UNUSED gchar
*line
, GList
*items
,
207 G_GNUC_UNUSED
void *data
)
209 if (g_list_length(items
) >= 1)
210 screen_display_completion_list(items
);
216 * Wrapper for strncmp(). We are not allowed to pass &strncmp to
217 * g_completion_set_compare(), because strncmp() takes size_t where
218 * g_completion_set_compare passes a gsize value.
221 completion_strncmp(const gchar
*s1
, const gchar
*s2
, gsize n
)
223 return strncmp(s1
, s2
, n
);
228 playlist_save(struct mpdclient
*c
, char *name
, char *defaultname
)
230 struct mpd_connection
*connection
;
231 gchar
*filename
, *filename_utf8
;
235 completion_callback_data_t data
;
244 /* initialize completion support */
245 gcmp
= g_completion_new(NULL
);
246 g_completion_set_compare(gcmp
, completion_strncmp
);
248 data
.dir_list
= NULL
;
250 wrln_completion_callback_data
= &data
;
251 wrln_pre_completion_callback
= save_pre_completion_cb
;
252 wrln_post_completion_callback
= save_post_completion_cb
;
255 /* query the user for a filename */
256 filename
= screen_readln(_("Save playlist as"),
261 /* destroy completion support */
262 wrln_completion_callback_data
= NULL
;
263 wrln_pre_completion_callback
= NULL
;
264 wrln_post_completion_callback
= NULL
;
265 g_completion_free(gcmp
);
266 list
= string_list_free(list
);
268 filename
=g_strstrip(filename
);
271 filename
=g_strdup(name
);
273 if (filename
== NULL
)
276 /* send save command to mpd */
278 connection
= mpdclient_get_connection(c
);
279 if (connection
== NULL
) {
284 filename_utf8
= locale_to_utf8(filename
);
285 if (!mpd_run_save(connection
, filename_utf8
)) {
286 if (mpd_connection_get_error(connection
) == MPD_ERROR_SERVER
&&
287 mpd_connection_get_server_error(connection
) == MPD_SERVER_ERROR_EXIST
&&
288 mpd_connection_clear_error(connection
)) {
292 buf
= g_strdup_printf(_("Replace %s [%s/%s] ? "),
294 key
= tolower(screen_getch(buf
));
298 g_free(filename_utf8
);
300 screen_status_printf(_("Aborted"));
304 if (!mpd_run_rm(connection
, filename_utf8
) ||
305 !mpd_run_save(connection
, filename_utf8
)) {
306 mpdclient_handle_error(c
);
307 g_free(filename_utf8
);
312 mpdclient_handle_error(c
);
313 g_free(filename_utf8
);
319 c
->events
|= MPD_IDLE_STORED_PLAYLIST
;
321 g_free(filename_utf8
);
324 screen_status_printf(_("Saved %s"), filename
);
330 static void add_dir(GCompletion
*gcmp
, gchar
*dir
, GList
**dir_list
,
331 GList
**list
, struct mpdclient
*c
)
333 g_completion_remove_items(gcmp
, *list
);
334 *list
= string_list_remove(*list
, dir
);
335 *list
= gcmp_list_from_path(c
, dir
, *list
, GCMP_TYPE_RFILE
);
336 g_completion_add_items(gcmp
, *list
);
337 *dir_list
= g_list_append(*dir_list
, g_strdup(dir
));
340 static void add_pre_completion_cb(GCompletion
*gcmp
, gchar
*line
, void *data
)
342 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
343 GList
**dir_list
= tmp
->dir_list
;
344 GList
**list
= tmp
->list
;
345 struct mpdclient
*c
= tmp
->c
;
348 /* create initial list */
349 *list
= gcmp_list_from_path(c
, "", NULL
, GCMP_TYPE_RFILE
);
350 g_completion_add_items(gcmp
, *list
);
351 } else if (line
&& line
[0] && line
[strlen(line
)-1]=='/' &&
352 string_list_find(*dir_list
, line
) == NULL
) {
353 /* add directory content to list */
354 add_dir(gcmp
, line
, dir_list
, list
, c
);
358 static void add_post_completion_cb(GCompletion
*gcmp
, gchar
*line
,
359 GList
*items
, void *data
)
361 completion_callback_data_t
*tmp
= (completion_callback_data_t
*)data
;
362 GList
**dir_list
= tmp
->dir_list
;
363 GList
**list
= tmp
->list
;
364 struct mpdclient
*c
= tmp
->c
;
366 if (g_list_length(items
) >= 1)
367 screen_display_completion_list(items
);
369 if (line
&& line
[0] && line
[strlen(line
) - 1] == '/' &&
370 string_list_find(*dir_list
, line
) == NULL
) {
371 /* add directory content to list */
372 add_dir(gcmp
, line
, dir_list
, list
, c
);
378 handle_add_to_playlist(struct mpdclient
*c
)
384 GList
*dir_list
= NULL
;
385 completion_callback_data_t data
;
387 /* initialize completion support */
388 gcmp
= g_completion_new(NULL
);
389 g_completion_set_compare(gcmp
, completion_strncmp
);
391 data
.dir_list
= &dir_list
;
393 wrln_completion_callback_data
= &data
;
394 wrln_pre_completion_callback
= add_pre_completion_cb
;
395 wrln_post_completion_callback
= add_post_completion_cb
;
401 path
= screen_readln(_("Add"),
406 /* destroy completion data */
408 wrln_completion_callback_data
= NULL
;
409 wrln_pre_completion_callback
= NULL
;
410 wrln_post_completion_callback
= NULL
;
411 g_completion_free(gcmp
);
412 string_list_free(list
);
413 string_list_free(dir_list
);
416 /* add the path to the playlist */
418 char *path_utf8
= locale_to_utf8(path
);
419 mpdclient_cmd_add_path(c
, path_utf8
);
428 screen_queue_init(WINDOW
*w
, int cols
, int rows
)
430 lw
= list_window_init(w
, cols
, rows
);
434 hscroll_init(&hscroll
, w
, options
.scroll_sep
);
439 timer_hide_cursor(gpointer data
)
441 struct mpdclient
*c
= data
;
443 assert(options
.hide_cursor
> 0);
444 assert(timer_hide_cursor_id
!= 0);
446 timer_hide_cursor_id
= 0;
448 /* hide the cursor when mpd is playing and the user is inactive */
450 if (c
->status
!= NULL
&&
451 mpd_status_get_state(c
->status
) == MPD_STATE_PLAY
) {
452 lw
->hide_cursor
= true;
453 screen_queue_repaint();
455 timer_hide_cursor_id
= g_timeout_add(options
.hide_cursor
* 1000,
456 timer_hide_cursor
, c
);
462 screen_queue_open(struct mpdclient
*c
)
464 playlist
= &c
->playlist
;
466 assert(timer_hide_cursor_id
== 0);
467 if (options
.hide_cursor
> 0) {
468 lw
->hide_cursor
= false;
469 timer_hide_cursor_id
= g_timeout_add(options
.hide_cursor
* 1000,
470 timer_hide_cursor
, c
);
473 screen_queue_restore_selection();
474 screen_queue_song_change(c
->status
);
478 screen_queue_close(void)
480 if (timer_hide_cursor_id
!= 0) {
481 g_source_remove(timer_hide_cursor_id
);
482 timer_hide_cursor_id
= 0;
487 hscroll_clear(&hscroll
);
492 screen_queue_resize(int cols
, int rows
)
494 list_window_resize(lw
, cols
, rows
);
499 screen_queue_exit(void)
501 list_window_free(lw
);
505 screen_queue_title(char *str
, size_t size
)
507 if (options
.host
== NULL
)
508 return _("Playlist");
510 g_snprintf(str
, size
, _("Playlist on %s"), options
.host
);
515 screen_queue_paint_callback(WINDOW
*w
, unsigned i
,
516 unsigned y
, unsigned width
,
517 bool selected
, G_GNUC_UNUSED
void *data
)
519 const struct mpd_song
*song
;
520 struct hscroll
*row_hscroll
;
522 assert(playlist
!= NULL
);
523 assert(i
< playlist_length(playlist
));
525 song
= playlist_get(playlist
, i
);
530 row_hscroll
= selected
&& options
.scroll
&& lw
->selected
== i
534 paint_song_row(w
, y
, width
, selected
,
535 (int)mpd_song_get_id(song
) == current_song_id
,
540 screen_queue_paint(void)
544 hscroll_clear(&hscroll
);
547 list_window_paint2(lw
, screen_queue_paint_callback
, NULL
);
551 screen_queue_update(struct mpdclient
*c
)
553 if (c
->events
& MPD_IDLE_QUEUE
)
554 screen_queue_restore_selection();
556 /* the queue size may have changed, even if we havn't
557 revceived the QUEUE idle event yet */
558 list_window_set_length(lw
, playlist_length(playlist
));
560 if (((c
->events
& MPD_IDLE_PLAYER
) != 0 &&
561 screen_queue_song_change(c
->status
)) ||
562 c
->events
& MPD_IDLE_QUEUE
)
563 /* the queue or the current song has changed, we must
564 paint the new version */
565 screen_queue_repaint();
570 handle_mouse_event(struct mpdclient
*c
)
573 unsigned long bstate
;
574 unsigned old_selected
;
576 if (screen_get_mouse_event(c
, &bstate
, &row
) ||
577 list_window_mouse(lw
, bstate
, row
)) {
578 screen_queue_repaint();
582 if (bstate
& BUTTON1_DOUBLE_CLICKED
) {
584 screen_cmd(c
, CMD_STOP
);
588 old_selected
= lw
->selected
;
589 list_window_set_cursor(lw
, lw
->start
+ row
);
591 if (bstate
& BUTTON1_CLICKED
) {
593 const struct mpd_song
*song
= screen_queue_selected_song();
595 struct mpd_connection
*connection
=
596 mpdclient_get_connection(c
);
598 if (connection
!= NULL
&&
599 !mpd_run_play_id(connection
,
600 mpd_song_get_id(song
)))
601 mpdclient_handle_error(c
);
603 } else if (bstate
& BUTTON3_CLICKED
) {
605 if (lw
->selected
== old_selected
)
606 mpdclient_cmd_delete(c
, lw
->selected
);
608 list_window_set_length(lw
, playlist_length(playlist
));
611 screen_queue_save_selection();
612 screen_queue_repaint();
619 screen_queue_cmd(struct mpdclient
*c
, command_t cmd
)
621 struct mpd_connection
*connection
;
622 static command_t cached_cmd
= CMD_NONE
;
623 command_t prev_cmd
= cached_cmd
;
624 struct list_window_range range
;
625 const struct mpd_song
*song
;
629 lw
->hide_cursor
= false;
631 if (options
.hide_cursor
> 0) {
632 if (timer_hide_cursor_id
!= 0)
633 g_source_remove(timer_hide_cursor_id
);
634 timer_hide_cursor_id
= g_timeout_add(options
.hide_cursor
* 1000,
635 timer_hide_cursor
, c
);
638 if (list_window_cmd(lw
, cmd
)) {
639 screen_queue_save_selection();
640 screen_queue_repaint();
645 case CMD_SCREEN_UPDATE
:
646 center_playing_item(c
->status
, prev_cmd
== CMD_SCREEN_UPDATE
);
647 screen_queue_repaint();
649 case CMD_SELECT_PLAYING
:
650 list_window_set_cursor(lw
, playlist_get_index(&c
->playlist
,
652 screen_queue_save_selection();
653 screen_queue_repaint();
658 case CMD_LIST_FIND_NEXT
:
659 case CMD_LIST_RFIND_NEXT
:
660 screen_find(lw
, cmd
, screen_queue_lw_callback
, NULL
);
661 screen_queue_save_selection();
662 screen_queue_repaint();
665 screen_jump(lw
, screen_queue_lw_callback
, NULL
, NULL
);
666 screen_queue_save_selection();
667 screen_queue_repaint();
671 case CMD_MOUSE_EVENT
:
672 return handle_mouse_event(c
);
675 #ifdef ENABLE_SONG_SCREEN
676 case CMD_SCREEN_SONG
:
677 if (screen_queue_selected_song() != NULL
) {
678 screen_song_switch(c
, screen_queue_selected_song());
685 #ifdef ENABLE_LYRICS_SCREEN
686 case CMD_SCREEN_LYRICS
:
687 if (lw
->selected
< playlist_length(&c
->playlist
)) {
688 struct mpd_song
*selected
= playlist_get(&c
->playlist
, lw
->selected
);
691 if (c
->song
&& selected
&&
692 !strcmp(mpd_song_get_uri(selected
),
693 mpd_song_get_uri(c
->song
)))
696 screen_lyrics_switch(c
, selected
, follow
);
702 case CMD_SCREEN_SWAP
:
703 screen_swap(c
, playlist_get(&c
->playlist
, lw
->selected
));
710 if (!mpdclient_is_connected(c
))
715 song
= screen_queue_selected_song();
719 connection
= mpdclient_get_connection(c
);
720 if (connection
!= NULL
&&
721 !mpd_run_play_id(connection
, mpd_song_get_id(song
)))
722 mpdclient_handle_error(c
);
727 list_window_get_range(lw
, &range
);
728 mpdclient_cmd_delete_range(c
, range
.start
, range
.end
);
730 list_window_set_cursor(lw
, range
.start
);
733 case CMD_SAVE_PLAYLIST
:
734 playlist_save(c
, NULL
, NULL
);
738 handle_add_to_playlist(c
);
742 list_window_get_range(lw
, &range
);
743 if (range
.end
<= range
.start
+ 1)
744 /* No range selection, shuffle all list. */
747 connection
= mpdclient_get_connection(c
);
748 if (connection
== NULL
)
751 if (mpd_run_shuffle_range(connection
, range
.start
, range
.end
))
752 screen_status_message(_("Shuffled playlist"));
754 mpdclient_handle_error(c
);
757 case CMD_LIST_MOVE_UP
:
758 list_window_get_range(lw
, &range
);
759 if (range
.start
== 0 || range
.end
<= range
.start
)
762 if (!mpdclient_cmd_move(c
, range
.end
- 1, range
.start
- 1))
768 if (lw
->range_selection
)
769 list_window_scroll_to(lw
, lw
->range_base
);
770 list_window_scroll_to(lw
, lw
->selected
);
772 screen_queue_save_selection();
775 case CMD_LIST_MOVE_DOWN
:
776 list_window_get_range(lw
, &range
);
777 if (range
.end
>= playlist_length(&c
->playlist
))
780 if (!mpdclient_cmd_move(c
, range
.start
, range
.end
))
786 if (lw
->range_selection
)
787 list_window_scroll_to(lw
, lw
->range_base
);
788 list_window_scroll_to(lw
, lw
->selected
);
790 screen_queue_save_selection();
794 if (screen_queue_selected_song() != NULL
) {
795 screen_file_goto_song(c
, screen_queue_selected_song());
808 const struct screen_functions screen_queue
= {
809 .init
= screen_queue_init
,
810 .exit
= screen_queue_exit
,
811 .open
= screen_queue_open
,
812 .close
= screen_queue_close
,
813 .resize
= screen_queue_resize
,
814 .paint
= screen_queue_paint
,
815 .update
= screen_queue_update
,
816 .cmd
= screen_queue_cmd
,
817 .get_title
= screen_queue_title
,