2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 #include <ui_curses.h>
22 #include <search_mode.h>
23 #include <command_mode.h>
25 #include <play_queue.h>
36 #include <format_print.h>
50 #include <sys/ioctl.h>
60 /* globals. documented in ui_curses.h */
62 int ui_initialized
= 0;
63 enum ui_input_mode input_mode
= NORMAL_MODE
;
64 int cur_view
= TREE_VIEW
;
65 struct searchable
*searchable
;
67 /* display parse errors? (command line) */
68 int display_errors
= 0;
70 char *lib_autosave_filename
;
71 char *pl_autosave_filename
;
72 char *lib_filename
= NULL
;
73 char *pl_filename
= NULL
;
75 /* ------------------------------------------------------------------------- */
77 /* currently playing file */
78 static struct track_info
*cur_track_info
;
80 static int update_window_title
= 0;
82 static int running
= 1;
84 /* shown error message and time stamp
85 * error is cleared if it is older than 3s and key was pressed
87 static char error_buf
[512];
88 static time_t error_time
= 0;
89 /* info messages are displayed in different color */
90 static int msg_is_error
;
91 static int error_count
= 0;
93 static char *server_address
= NULL
;
94 static int remote_socket
= -1;
96 static char *charset
= NULL
;
97 static char print_buffer
[512];
99 /* destination buffer for utf8_encode and utf8_decode */
100 static char conv_buffer
[512];
102 #define print_buffer_size (sizeof(print_buffer) - 1)
103 static int using_utf8
;
105 static int tree_win_x
= 0;
106 static int tree_win_y
= 0;
107 static int tree_win_w
= 0;
109 static int track_win_x
= 0;
110 static int track_win_y
= 0;
111 static int track_win_w
= 0;
120 CURSED_WIN_ACTIVE_CUR
,
121 CURSED_WIN_ACTIVE_SEL
,
122 CURSED_WIN_ACTIVE_SEL_CUR
,
137 static unsigned char cursed_to_bg_idx
[NR_CURSED
] = {
140 COLOR_WIN_INACTIVE_SEL_BG
,
141 COLOR_WIN_INACTIVE_CUR_SEL_BG
,
146 COLOR_WIN_CUR_SEL_BG
,
159 static unsigned char cursed_to_fg_idx
[NR_CURSED
] = {
162 COLOR_WIN_INACTIVE_SEL_FG
,
163 COLOR_WIN_INACTIVE_CUR_SEL_FG
,
168 COLOR_WIN_CUR_SEL_FG
,
181 /* index is CURSED_*, value is fucking color pair */
182 static int pairs
[NR_CURSED
];
198 static struct format_option track_fopts
[NR_TFS
+ 1] = {
228 static struct format_option status_fopts
[NR_SFS
+ 1] = {
244 static void utf8_encode(const char *buffer
)
246 static iconv_t cd
= (iconv_t
)-1;
252 if (cd
== (iconv_t
)-1) {
253 d_print("iconv_open(UTF-8, %s)\n", charset
);
254 cd
= iconv_open("UTF-8", charset
);
255 if (cd
== (iconv_t
)-1) {
256 d_print("iconv_open failed: %s\n", strerror(errno
));
263 os
= sizeof(conv_buffer
) - 1;
264 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
267 d_print("iconv failed: %s\n", strerror(errno
));
272 static void utf8_decode(const char *buffer
)
274 static iconv_t cd
= (iconv_t
)-1;
280 if (cd
== (iconv_t
)-1) {
281 d_print("iconv_open(%s, UTF-8)\n", charset
);
282 cd
= iconv_open(charset
, "UTF-8");
283 if (cd
== (iconv_t
)-1) {
284 d_print("iconv_open failed: %s\n", strerror(errno
));
291 os
= sizeof(conv_buffer
) - 1;
292 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
295 d_print("iconv failed: %s\n", strerror(errno
));
300 /* screen updates {{{ */
302 static void dump_print_buffer(int row
, int col
)
305 mvaddstr(row
, col
, print_buffer
);
307 utf8_decode(print_buffer
);
308 mvaddstr(row
, col
, conv_buffer
);
312 static void sprint(int row
, int col
, const char *str
, int width
, int indent
)
314 int s
, d
, ellipsis_pos
= 0, cut_double_width
= 0;
318 memset(print_buffer
, ' ', d
);
327 u_get_char(str
, &s
, &u
);
329 memset(print_buffer
+ d
, ' ', width
);
337 if (width
== 4 && w
== 2) {
338 /* can't cut double-width char */
339 ellipsis_pos
= d
+ 1;
340 cut_double_width
= 1;
347 if (cut_double_width
) {
348 /* first half of the double-width char */
349 print_buffer
[d
- 1] = ' ';
351 print_buffer
[d
++] = '.';
352 print_buffer
[d
++] = '.';
353 print_buffer
[d
++] = '.';
356 u_set_char(print_buffer
, &d
, u
);
358 print_buffer
[d
++] = ' ';
359 print_buffer
[d
++] = 0;
360 dump_print_buffer(row
, col
);
363 static void sprint_ascii(int row
, int col
, const char *str
, int len
)
370 print_buffer
[0] = ' ';
372 memcpy(print_buffer
+ 1, str
, len
- 3);
373 print_buffer
[len
- 2] = '.';
374 print_buffer
[len
- 1] = '.';
375 print_buffer
[len
- 0] = '.';
377 memcpy(print_buffer
+ 1, str
, l
);
378 memset(print_buffer
+ 1 + l
, ' ', len
- l
);
380 print_buffer
[len
+ 1] = ' ';
381 print_buffer
[len
+ 2] = 0;
382 mvaddstr(row
, col
, print_buffer
);
385 static void print_tree(struct window
*win
, int row
, struct iter
*iter
)
387 const char *noname
= "<no name>";
388 struct artist
*artist
;
391 int current
, selected
, active
;
393 artist
= iter_to_artist(iter
);
394 album
= iter_to_album(iter
);
396 current
= lib
.cur_album
== album
;
398 current
= lib
.cur_artist
== artist
;
400 window_get_sel(win
, &sel
);
401 selected
= iters_equal(iter
, &sel
);
402 active
= lib
.cur_win
== lib
.tree_win
;
403 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
405 sprint(tree_win_y
+ row
+ 1, tree_win_x
, album
->name
? : noname
, tree_win_w
, 2);
407 sprint(tree_win_y
+ row
+ 1, tree_win_x
, artist
->name
? : noname
, tree_win_w
, 0);
411 static inline void fopt_set_str(struct format_option
*fopt
, const char *str
)
413 BUG_ON(fopt
->type
!= FO_STR
);
422 static inline void fopt_set_int(struct format_option
*fopt
, int value
, int empty
)
424 BUG_ON(fopt
->type
!= FO_INT
);
425 fopt
->fo_int
= value
;
429 static inline void fopt_set_time(struct format_option
*fopt
, int value
, int empty
)
431 BUG_ON(fopt
->type
!= FO_TIME
);
432 fopt
->fo_time
= value
;
436 static void fill_track_fopts(struct tree_track
*track
)
438 const char *filename
;
439 const struct track_info
*ti
= tree_track_info(track
);
443 filename
= ti
->filename
;
445 utf8_encode(ti
->filename
);
446 filename
= conv_buffer
;
448 disc
= track
->shuffle_track
.simple_track
.disc
;
449 num
= track
->shuffle_track
.simple_track
.num
;
451 fopt_set_str(&track_fopts
[TF_ARTIST
], track
->album
->artist
->name
);
452 fopt_set_str(&track_fopts
[TF_ALBUM
], track
->album
->name
);
453 fopt_set_int(&track_fopts
[TF_DISC
], disc
, disc
== -1);
454 fopt_set_int(&track_fopts
[TF_TRACK
], num
, num
== -1);
455 fopt_set_str(&track_fopts
[TF_TITLE
], comments_get_val(ti
->comments
, "title"));
456 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(ti
->comments
, "date"));
457 fopt_set_str(&track_fopts
[TF_GENRE
], comments_get_val(ti
->comments
, "genre"));
458 fopt_set_time(&track_fopts
[TF_DURATION
], ti
->duration
, ti
->duration
== -1);
459 fopt_set_str(&track_fopts
[TF_PATHFILE
], filename
);
460 if (is_url(ti
->filename
)) {
461 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
465 f
= strrchr(filename
, '/');
467 fopt_set_str(&track_fopts
[TF_FILE
], f
+ 1);
469 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
474 static void fill_track_fopts_track_info(struct track_info
*info
)
480 filename
= info
->filename
;
482 utf8_encode(info
->filename
);
483 filename
= conv_buffer
;
485 disc
= comments_get_int(info
->comments
, "discnumber");
486 num
= comments_get_int(info
->comments
, "tracknumber");
488 fopt_set_str(&track_fopts
[TF_ARTIST
], comments_get_val(info
->comments
, "artist"));
489 fopt_set_str(&track_fopts
[TF_ALBUM
], comments_get_val(info
->comments
, "album"));
490 fopt_set_int(&track_fopts
[TF_DISC
], disc
, disc
== -1);
491 fopt_set_int(&track_fopts
[TF_TRACK
], num
, num
== -1);
492 fopt_set_str(&track_fopts
[TF_TITLE
], comments_get_val(info
->comments
, "title"));
493 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(info
->comments
, "date"));
494 fopt_set_str(&track_fopts
[TF_GENRE
], comments_get_val(info
->comments
, "genre"));
495 fopt_set_time(&track_fopts
[TF_DURATION
], info
->duration
, info
->duration
== -1);
496 fopt_set_str(&track_fopts
[TF_PATHFILE
], filename
);
497 if (is_url(info
->filename
)) {
498 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
502 f
= strrchr(filename
, '/');
504 fopt_set_str(&track_fopts
[TF_FILE
], f
+ 1);
506 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
511 static void print_track(struct window
*win
, int row
, struct iter
*iter
)
513 struct tree_track
*track
;
515 int current
, selected
, active
;
517 track
= iter_to_tree_track(iter
);
518 current
= lib
.cur_track
== track
;
519 window_get_sel(win
, &sel
);
520 selected
= iters_equal(iter
, &sel
);
521 active
= lib
.cur_win
== lib
.track_win
;
522 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
524 fill_track_fopts(track
);
526 if (track_info_has_tag(tree_track_info(track
))) {
527 format_print(print_buffer
, track_win_w
, track_win_format
, track_fopts
);
529 format_print(print_buffer
, track_win_w
, track_win_alt_format
, track_fopts
);
531 dump_print_buffer(track_win_y
+ row
+ 1, track_win_x
);
534 static void print_sorted(struct window
*win
, int row
, struct iter
*iter
)
536 struct tree_track
*track
;
538 int current
, selected
, active
= 1;
540 track
= iter_to_sorted_track(iter
);
541 current
= lib
.cur_track
== track
;
542 window_get_sel(win
, &sel
);
543 selected
= iters_equal(iter
, &sel
);
544 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
546 fill_track_fopts(track
);
548 if (track_info_has_tag(tree_track_info(track
))) {
549 format_print(print_buffer
, COLS
, list_win_format
, track_fopts
);
551 format_print(print_buffer
, COLS
, list_win_alt_format
, track_fopts
);
553 dump_print_buffer(row
+ 1, 0);
556 static void print_pl(struct window
*win
, int row
, struct iter
*iter
)
558 struct simple_track
*track
;
560 int current
, selected
, active
;
562 track
= iter_to_simple_track(iter
);
563 current
= pl_cur_track
== track
;
564 window_get_sel(win
, &sel
);
565 selected
= iters_equal(iter
, &sel
);
568 if (!selected
&& track
->marked
) {
573 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
575 fill_track_fopts_track_info(track
->info
);
577 if (track_info_has_tag(track
->info
)) {
578 format_print(print_buffer
, COLS
, list_win_format
, track_fopts
);
580 format_print(print_buffer
, COLS
, list_win_alt_format
, track_fopts
);
582 dump_print_buffer(row
+ 1, 0);
585 static void print_play_queue(struct window
*win
, int row
, struct iter
*iter
)
587 struct simple_track
*track
;
589 int current
, selected
, active
;
591 track
= iter_to_simple_track(iter
);
593 window_get_sel(win
, &sel
);
594 selected
= iters_equal(iter
, &sel
);
597 if (!selected
&& track
->marked
) {
602 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
604 fill_track_fopts_track_info(track
->info
);
606 if (track_info_has_tag(track
->info
)) {
607 format_print(print_buffer
, COLS
, list_win_format
, track_fopts
);
609 format_print(print_buffer
, COLS
, list_win_alt_format
, track_fopts
);
611 dump_print_buffer(row
+ 1, 0);
614 static void print_browser(struct window
*win
, int row
, struct iter
*iter
)
616 struct browser_entry
*e
;
620 e
= iter_to_browser_entry(iter
);
621 window_get_sel(win
, &sel
);
622 selected
= iters_equal(iter
, &sel
);
627 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
629 if (e
->type
== BROWSER_ENTRY_DIR
) {
630 bkgdset(pairs
[CURSED_DIR
]);
632 bkgdset(pairs
[CURSED_WIN
]);
636 /* file name encoding == terminal encoding. no need to convert */
638 sprint(row
+ 1, 0, e
->name
, COLS
, 0);
640 sprint_ascii(row
+ 1, 0, e
->name
, COLS
);
644 static void print_filter(struct window
*win
, int row
, struct iter
*iter
)
647 struct filter_entry
*e
= iter_to_filter_entry(iter
);
653 /* is the filter currently active? */
654 int current
= e
->active
;
656 window_get_sel(win
, &sel
);
657 selected
= iters_equal(iter
, &sel
);
658 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
660 snprintf(buf
, sizeof(buf
), "%c %-15s %s", e
->selected
? '*' : ' ', e
->name
, e
->filter
);
661 sprint(row
+ 1, 0, buf
, COLS
, 0);
664 static void update_window(struct window
*win
, int x
, int y
, int w
, const char *title
,
665 void (*print
)(struct window
*, int, struct iter
*))
673 bkgdset(pairs
[CURSED_WIN_TITLE
]);
674 c
= snprintf(print_buffer
, w
+ 1, " %s", title
);
677 memset(print_buffer
+ c
, ' ', w
- c
+ 1);
679 dump_print_buffer(y
, x
);
680 nr_rows
= window_get_nr_rows(win
);
682 if (window_get_top(win
, &iter
)) {
683 while (i
< nr_rows
) {
684 print(win
, i
, &iter
);
686 if (!window_get_next(win
, &iter
))
692 memset(print_buffer
, ' ', w
);
694 while (i
< nr_rows
) {
695 dump_print_buffer(y
+ i
+ 1, x
);
700 static void update_tree_window(void)
702 update_window(lib
.tree_win
, tree_win_x
, tree_win_y
,
703 tree_win_w
, "Artist / Album", print_tree
);
706 static void update_track_window(void)
710 format_print(title
, track_win_w
- 2, "Track%=Library", track_fopts
);
711 update_window(lib
.track_win
, track_win_x
, track_win_y
,
712 track_win_w
, title
, print_track
);
715 static const char *pretty(const char *path
)
717 static int home_len
= -1;
718 static char buf
[256];
721 home_len
= strlen(home_dir
);
723 if (strncmp(path
, home_dir
, home_len
) || path
[home_len
] != '/')
727 strcpy(buf
+ 1, path
+ home_len
);
731 static const char * const sorted_names
[2] = { "", "sorted by " };
733 static void update_sorted_window(void)
738 filename
= lib_filename
? lib_filename
: lib_autosave_filename
;
742 utf8_encode(filename
);
743 filename
= conv_buffer
;
745 snprintf(title
, sizeof(title
), "Library %s - %d tracks %s%s", pretty(filename
),
746 lib
.nr_tracks
, sorted_names
[lib_sort_str
[0] != 0], lib_sort_str
);
747 update_window(lib
.sorted_win
, 0, 0, COLS
, title
, print_sorted
);
750 static void update_pl_window(void)
756 filename
= pl_filename
? pl_filename
: pl_autosave_filename
;
760 utf8_encode(filename
);
761 filename
= conv_buffer
;
764 snprintf(title
, sizeof(title
), "Playlist %s - %d tracks", pretty(filename
), pl_nr_tracks
);
767 snprintf(title
+ pos
, sizeof(title
) - pos
, " (%d marked)", pl_nr_marked
);
770 snprintf(title
+ pos
, sizeof(title
) - pos
, " %s%s",
771 sorted_names
[pl_sort_str
[0] != 0], pl_sort_str
);
773 update_window(pl_win
, 0, 0, COLS
, title
, print_pl
);
776 static void update_play_queue_window(void)
780 snprintf(title
, sizeof(title
), "Play Queue - %d tracks", pq_nr_tracks
);
782 int pos
= strlen(title
);
784 snprintf(title
+ pos
, sizeof(title
) - pos
, " (%d marked)", pq_nr_marked
);
786 update_window(play_queue_win
, 0, 0, COLS
, title
, print_play_queue
);
789 static void update_browser_window(void)
796 dirname
= browser_dir
;
798 utf8_encode(browser_dir
);
799 dirname
= conv_buffer
;
801 snprintf(title
, sizeof(title
), "Browser - %s", dirname
);
802 update_window(browser_win
, 0, 0, COLS
, title
, print_browser
);
805 static void update_filters_window(void)
807 update_window(filters_win
, 0, 0, COLS
, "Library Filters", print_filter
);
810 static void draw_separator(void)
814 bkgdset(pairs
[CURSED_WIN_TITLE
]);
815 mvaddch(0, tree_win_w
, ' ');
816 bkgdset(pairs
[CURSED_SEPARATOR
]);
817 for (row
= 1; row
< LINES
- 3; row
++)
818 mvaddch(row
, tree_win_w
, ACS_VLINE
);
821 static void do_update_view(int full
)
826 if (full
|| lib
.tree_win
->changed
)
827 update_tree_window();
828 if (full
|| lib
.track_win
->changed
)
829 update_track_window();
835 update_sorted_window();
845 update_play_queue_window();
849 update_browser_window();
852 update_filters_window();
857 static void do_update_statusline(void)
859 static const char *status_strs
[] = { ".", ">", "|" };
860 static const char *cont_strs
[] = { " ", "C" };
861 static const char *repeat_strs
[] = { " ", "R" };
862 static const char *shuffle_strs
[] = { " ", "S" };
863 int play_sorted
, buffer_fill
, vol
, vol_left
, vol_right
;
869 fopt_set_time(&status_fopts
[SF_TOTAL
], play_library
? lib
.total_time
: pl_total_time
, 0);
870 fopt_set_str(&status_fopts
[SF_REPEAT
], repeat_strs
[repeat
]);
871 fopt_set_str(&status_fopts
[SF_SHUFFLE
], shuffle_strs
[shuffle
]);
872 fopt_set_str(&status_fopts
[SF_PLAYLISTMODE
], aaa_mode_names
[lib
.aaa_mode
]);
873 play_sorted
= lib
.play_sorted
;
877 duration
= cur_track_info
->duration
;
881 vol_left
= scale_to_percentage(player_info
.vol_left
, player_info
.vol_max
);
882 vol_right
= scale_to_percentage(player_info
.vol_right
, player_info
.vol_max
);
883 vol
= (vol_left
+ vol_right
+ 1) / 2;
884 buffer_fill
= scale_to_percentage(player_info
.buffer_fill
, player_info
.buffer_size
);
886 fopt_set_str(&status_fopts
[SF_STATUS
], status_strs
[player_info
.status
]);
888 if (show_remaining_time
&& duration
!= -1) {
889 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
- duration
, 0);
891 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
, 0);
894 fopt_set_time(&status_fopts
[SF_DURATION
], duration
, 0);
895 fopt_set_int(&status_fopts
[SF_VOLUME
], vol
, 0);
896 fopt_set_int(&status_fopts
[SF_LVOLUME
], vol_left
, 0);
897 fopt_set_int(&status_fopts
[SF_RVOLUME
], vol_right
, 0);
898 fopt_set_int(&status_fopts
[SF_BUFFER
], buffer_fill
, 0);
899 fopt_set_str(&status_fopts
[SF_CONTINUE
], cont_strs
[player_cont
]);
901 strcpy(format
, " %s %p ");
903 strcat(format
, "/ %d ");
904 if (player_info
.vol_left
!= player_info
.vol_right
) {
905 strcat(format
, "- %t vol: %l,%r ");
907 strcat(format
, "- %t vol: %v ");
909 if (cur_track_info
&& is_url(cur_track_info
->filename
))
910 strcat(format
, "buf: %b ");
911 strcat(format
, "%=");
913 /* artist/album modes work only in lib */
915 /* shuffle overrides sorted mode */
916 strcat(format
, "%L from library");
917 } else if (play_sorted
) {
918 strcat(format
, "%L from sorted library");
920 strcat(format
, "%L from library");
923 strcat(format
, "playlist");
925 strcat(format
, " | %1C%1R%1S ");
926 format_print(print_buffer
, COLS
, format
, status_fopts
);
928 msg
= player_info
.error_msg
;
929 player_info
.error_msg
= NULL
;
931 player_info_unlock();
933 bkgdset(pairs
[CURSED_STATUSLINE
]);
934 dump_print_buffer(LINES
- 2, 0);
937 error_msg("%s", msg
);
942 static void dump_buffer(const char *buffer
)
952 static void do_update_commandline(void)
960 bkgdset(pairs
[CURSED_ERROR
]);
962 bkgdset(pairs
[CURSED_INFO
]);
968 bkgdset(pairs
[CURSED_COMMANDLINE
]);
969 if (input_mode
== NORMAL_MODE
) {
974 /* COMMAND_MODE or SEARCH_MODE */
975 w
= u_str_width(cmdline
.line
);
977 if (input_mode
== SEARCH_MODE
)
978 ch
= search_direction
== SEARCH_FORWARD
? '/' : '?';
982 idx
= u_copy_chars(print_buffer
, cmdline
.line
, &w
);
983 print_buffer
[idx
] = 0;
984 dump_buffer(print_buffer
);
987 /* keep cursor as far right as possible */
990 /* cursor pos (width, not chars. doesn't count the ':') */
991 cw
= u_str_nwidth(cmdline
.line
, cmdline
.cpos
);
993 skip
= cw
+ 2 - COLS
;
998 /* skip rest (if any) */
999 idx
= u_skip_chars(cmdline
.line
, &skip
);
1002 idx
= u_copy_chars(print_buffer
, cmdline
.line
+ idx
, &width
);
1003 while (width
< COLS
) {
1004 /* cursor is at end of the buffer
1005 * print a space (or 2 if the last skipped character
1008 print_buffer
[idx
++] = ' ';
1011 print_buffer
[idx
] = 0;
1012 dump_buffer(print_buffer
);
1014 /* print ':' + COLS - 1 chars */
1017 idx
= u_copy_chars(print_buffer
, cmdline
.line
, &width
);
1018 print_buffer
[idx
] = 0;
1019 dump_buffer(print_buffer
);
1024 /* lock player_info! */
1025 static const char *get_stream_title(const char *metadata
)
1027 static char stream_title
[255 * 16 + 1];
1030 ptr
= strstr(player_info
.metadata
, "StreamTitle='");
1036 if (*ptr
== '\'' && *(ptr
+ 1) == ';') {
1037 memcpy(stream_title
, title
, ptr
- title
);
1038 stream_title
[ptr
- title
] = 0;
1039 return stream_title
;
1046 static void do_update_titleline(void)
1048 bkgdset(pairs
[CURSED_TITLELINE
]);
1050 if (cur_track_info
) {
1051 const char *filename
;
1052 int use_alt_format
= 0;
1053 struct keyval
*cur_comments
= cur_track_info
->comments
;
1055 if (cur_comments
[0].key
== NULL
) {
1056 const char *title
= get_stream_title(player_info
.metadata
);
1060 fopt_set_str(&track_fopts
[TF_ARTIST
], NULL
);
1061 fopt_set_str(&track_fopts
[TF_ALBUM
], NULL
);
1062 fopt_set_int(&track_fopts
[TF_DISC
], -1, 1);
1063 fopt_set_int(&track_fopts
[TF_TRACK
], -1, 1);
1064 fopt_set_str(&track_fopts
[TF_TITLE
], title
);
1065 fopt_set_str(&track_fopts
[TF_YEAR
], NULL
);
1067 int disc_num
, track_num
;
1069 disc_num
= comments_get_int(cur_comments
, "discnumber");
1070 track_num
= comments_get_int(cur_comments
, "tracknumber");
1071 fopt_set_str(&track_fopts
[TF_ARTIST
], comments_get_val(cur_comments
, "artist"));
1072 fopt_set_str(&track_fopts
[TF_ALBUM
], comments_get_val(cur_comments
, "album"));
1073 fopt_set_int(&track_fopts
[TF_DISC
], disc_num
, disc_num
== -1);
1074 fopt_set_int(&track_fopts
[TF_TRACK
], track_num
, track_num
== -1);
1075 fopt_set_str(&track_fopts
[TF_TITLE
], comments_get_val(cur_comments
, "title"));
1076 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(cur_comments
, "date"));
1078 fopt_set_time(&track_fopts
[TF_DURATION
],
1079 cur_track_info
->duration
,
1080 cur_track_info
->duration
== -1);
1081 fopt_set_str(&track_fopts
[TF_PATHFILE
], cur_track_info
->filename
);
1082 if (is_url(cur_track_info
->filename
)) {
1083 fopt_set_str(&track_fopts
[TF_FILE
], cur_track_info
->filename
);
1085 filename
= strrchr(cur_track_info
->filename
, '/');
1087 fopt_set_str(&track_fopts
[TF_FILE
], filename
+ 1);
1089 fopt_set_str(&track_fopts
[TF_FILE
], cur_track_info
->filename
);
1092 if (use_alt_format
) {
1093 format_print(print_buffer
, COLS
, current_alt_format
, track_fopts
);
1095 format_print(print_buffer
, COLS
, current_format
, track_fopts
);
1097 dump_print_buffer(LINES
- 3, 0);
1099 if (update_window_title
) {
1103 if (use_alt_format
) {
1104 format_print(print_buffer
, sizeof(print_buffer
) - 1,
1105 window_title_alt_format
, track_fopts
);
1107 format_print(print_buffer
, sizeof(print_buffer
) - 1,
1108 window_title_format
, track_fopts
);
1111 /* remove whitespace */
1112 i
= sizeof(print_buffer
) - 2;
1113 while (i
> 0 && print_buffer
[i
] == ' ')
1115 print_buffer
[i
+ 1] = 0;
1118 wtitle
= print_buffer
;
1120 utf8_decode(print_buffer
);
1121 wtitle
= conv_buffer
;
1124 printf("\033]0;%s\007", wtitle
);
1131 if (update_window_title
) {
1132 printf("\033]0;CMus " VERSION
"\007");
1136 player_info_unlock();
1139 static int cmdline_cursor_column(void)
1143 /* width of the text in the buffer before cursor */
1144 cw
= u_str_nwidth(cmdline
.line
, cmdline
.cpos
);
1146 if (1 + cw
< COLS
) {
1147 /* whole line is visible */
1151 /* beginning of cmdline is not visible */
1153 /* check if the first visible char in cmdline would be halved
1154 * double-width character (or invalid byte <xx>) which is not possible.
1155 * we need to skip the whole character and move cursor to COLS - 2
1157 skip
= cw
+ 2 - COLS
;
1164 u_skip_chars(cmdline
.line
, &s
);
1166 /* the last skipped char was double-width or <xx> */
1167 return COLS
- 1 - (s
- skip
);
1172 static void post_update(void)
1174 /* refresh makes cursor visible at least for urxvt */
1175 if (input_mode
== COMMAND_MODE
|| input_mode
== SEARCH_MODE
) {
1176 move(LINES
- 1, cmdline_cursor_column());
1186 void update_titleline(void)
1189 do_update_titleline();
1193 void update_full(void)
1195 if (!ui_initialized
)
1201 do_update_titleline();
1202 do_update_statusline();
1203 do_update_commandline();
1208 static void update_commandline(void)
1211 do_update_commandline();
1215 void update_statusline(void)
1217 if (!ui_initialized
)
1221 do_update_statusline();
1225 void info_msg(const char *format
, ...)
1229 va_start(ap
, format
);
1230 vsnprintf(error_buf
, sizeof(error_buf
), format
, ap
);
1235 update_commandline();
1238 void error_msg(const char *format
, ...)
1242 if (!display_errors
)
1245 strcpy(error_buf
, "Error: ");
1246 va_start(ap
, format
);
1247 vsnprintf(error_buf
+ 7, sizeof(error_buf
) - 7, format
, ap
);
1253 if (ui_initialized
) {
1254 error_time
= time(NULL
);
1255 update_commandline();
1257 warn("%s\n", error_buf
);
1262 int yes_no_query(const char *format
, ...)
1268 va_start(ap
, format
);
1269 vsnprintf(buffer
, sizeof(buffer
), format
, ap
);
1273 bkgdset(pairs
[CURSED_INFO
]);
1275 /* no need to convert buffer.
1276 * it is always encoded in the right charset (assuming filenames are
1277 * encoded in same charset as LC_CTYPE).
1287 if (ch
== ERR
|| ch
== 0)
1293 update_commandline();
1297 void search_not_found(void)
1299 const char *what
= "Track";
1301 if (search_restricted
) {
1304 what
= "Artist/album";
1312 what
= "File/Directory";
1327 what
= "File/Directory";
1334 info_msg("%s not found: %s", what
, search_str
? : "");
1337 void set_view(int view
)
1339 if (view
== cur_view
)
1345 __lib_set_view(view
);
1348 searchable
= tree_searchable
;
1349 update_tree_window();
1350 update_track_window();
1354 searchable
= sorted_searchable
;
1355 update_sorted_window();
1358 searchable
= pl_searchable
;
1362 searchable
= play_queue_searchable
;
1363 update_play_queue_window();
1366 searchable
= browser_searchable
;
1367 update_browser_window();
1370 searchable
= filters_searchable
;
1371 update_filters_window();
1379 void enter_command_mode(void)
1383 input_mode
= COMMAND_MODE
;
1384 update_commandline();
1387 void enter_search_mode(void)
1391 input_mode
= SEARCH_MODE
;
1392 search_direction
= SEARCH_FORWARD
;
1393 update_commandline();
1396 void enter_search_backward_mode(void)
1400 input_mode
= SEARCH_MODE
;
1401 search_direction
= SEARCH_BACKWARD
;
1402 update_commandline();
1410 void update_colors(void)
1414 if (!ui_initialized
)
1417 for (i
= 0; i
< NR_CURSED
; i
++) {
1418 int bg
= colors
[cursed_to_bg_idx
[i
]];
1419 int fg
= colors
[cursed_to_fg_idx
[i
]];
1422 if (fg
>= 8 && fg
<= 15) {
1423 /* fg colors 8..15 are special (0..7 + bold) */
1424 init_pair(pair
, fg
& 7, bg
);
1425 pairs
[i
] = COLOR_PAIR(pair
) | (fg
& BRIGHT
? A_BOLD
: 0);
1427 init_pair(pair
, fg
, bg
);
1428 pairs
[i
] = COLOR_PAIR(pair
);
1434 do_update_titleline();
1435 do_update_statusline();
1436 do_update_commandline();
1441 static void clear_error(void)
1443 time_t t
= time(NULL
);
1445 /* error msg is visible at least 3s */
1446 if (t
- error_time
< 3)
1452 update_commandline();
1456 /* screen updates }}} */
1458 static void spawn_status_program(void)
1460 static const char *status_strs
[] = { "stopped", "playing", "paused" };
1461 const char *stream_title
= NULL
;
1465 if (status_display_program
== NULL
|| status_display_program
[0] == 0)
1469 status
= player_info
.status
;
1471 stream_title
= get_stream_title(player_info
.metadata
);
1472 player_info_unlock();
1475 argv
[i
++] = xstrdup(status_display_program
);
1477 argv
[i
++] = xstrdup("status");
1478 argv
[i
++] = xstrdup(status_strs
[status
]);
1479 if (cur_track_info
) {
1480 static const char *keys
[] = {
1481 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1485 if (is_url(cur_track_info
->filename
)) {
1486 argv
[i
++] = xstrdup("url");
1487 argv
[i
++] = xstrdup(cur_track_info
->filename
);
1489 argv
[i
++] = xstrdup("title");
1490 argv
[i
++] = xstrdup(stream_title
);
1495 argv
[i
++] = xstrdup("file");
1496 argv
[i
++] = xstrdup(cur_track_info
->filename
);
1497 for (j
= 0; keys
[j
]; j
++) {
1498 const char *key
= keys
[j
];
1501 val
= comments_get_val(cur_track_info
->comments
, key
);
1503 argv
[i
++] = xstrdup(key
);
1504 argv
[i
++] = xstrdup(val
);
1507 snprintf(buf
, sizeof(buf
), "%d", cur_track_info
->duration
);
1508 argv
[i
++] = xstrdup("duration");
1509 argv
[i
++] = xstrdup(buf
);
1513 if (spawn(argv
, &status
) == -1)
1514 error_msg("couldn't run `%s': %s", status_display_program
, strerror(errno
));
1515 for (i
= 0; argv
[i
]; i
++)
1519 static int ctrl_c_pressed
= 0;
1521 static void sig_int(int sig
)
1526 static int needs_to_resize
= 1;
1528 static void sig_winch(int sig
)
1530 needs_to_resize
= 1;
1533 static int get_window_size(int *lines
, int *columns
)
1537 if (ioctl(0, TIOCGWINSZ
, &ws
) == -1)
1539 *columns
= ws
.ws_col
;
1544 static void resize_playlist(int w
, int h
)
1547 track_win_w
= w
- tree_win_w
- 1;
1550 if (track_win_w
< 8)
1554 track_win_x
= tree_win_w
+ 1;
1559 window_set_nr_rows(lib
.tree_win
, h
);
1560 window_set_nr_rows(lib
.track_win
, h
);
1561 window_set_nr_rows(lib
.sorted_win
, h
);
1565 static void update(void)
1567 int needs_view_update
= 0;
1568 int needs_title_update
= 0;
1569 int needs_status_update
= 0;
1570 int needs_command_update
= 0;
1571 int needs_spawn
= 0;
1573 if (needs_to_resize
) {
1577 if (get_window_size(&lines
, &columns
) == 0) {
1578 needs_to_resize
= 0;
1579 resizeterm(lines
, columns
);
1586 resize_playlist(w
, h
);
1587 window_set_nr_rows(filters_win
, h
- 1);
1588 window_set_nr_rows(browser_win
, h
- 1);
1589 window_set_nr_rows(play_queue_win
, h
- 1);
1590 window_set_nr_rows(pl_win
, h
- 1);
1591 needs_title_update
= 1;
1592 needs_status_update
= 1;
1593 needs_command_update
= 1;
1600 needs_spawn
= player_info
.status_changed
|| player_info
.file_changed
||
1601 player_info
.metadata_changed
;
1603 if (player_info
.file_changed
) {
1605 track_info_unref(cur_track_info
);
1606 if (player_info
.filename
[0] == 0) {
1607 cur_track_info
= NULL
;
1609 cur_track_info
= cmus_get_track_info(player_info
.filename
);
1611 player_info
.file_changed
= 0;
1612 needs_title_update
= 1;
1613 needs_status_update
= 1;
1615 if (player_info
.metadata_changed
) {
1616 player_info
.metadata_changed
= 0;
1617 needs_title_update
= 1;
1619 if (player_info
.position_changed
|| player_info
.status_changed
|| player_info
.vol_changed
) {
1620 player_info
.position_changed
= 0;
1621 player_info
.status_changed
= 0;
1622 player_info
.vol_changed
= 0;
1624 needs_status_update
= 1;
1628 needs_view_update
+= lib
.tree_win
->changed
|| lib
.track_win
->changed
;
1631 needs_view_update
+= lib
.sorted_win
->changed
;
1634 needs_view_update
+= pl_win
->changed
;
1637 needs_view_update
+= play_queue_win
->changed
;
1640 needs_view_update
+= browser_win
->changed
;
1643 needs_view_update
+= filters_win
->changed
;
1647 /* total time changed? */
1649 needs_status_update
+= lib
.sorted_win
->changed
;
1651 needs_status_update
+= pl_win
->changed
;
1655 player_info_unlock();
1658 spawn_status_program();
1660 if (needs_view_update
|| needs_title_update
|| needs_status_update
|| needs_command_update
) {
1663 if (needs_view_update
)
1665 if (needs_title_update
)
1666 do_update_titleline();
1667 if (needs_status_update
)
1668 do_update_statusline();
1669 if (needs_command_update
)
1670 do_update_commandline();
1675 static void handle_ch(uchar ch
)
1678 if (input_mode
== NORMAL_MODE
) {
1680 } else if (input_mode
== COMMAND_MODE
) {
1681 command_mode_ch(ch
);
1682 update_commandline();
1683 } else if (input_mode
== SEARCH_MODE
) {
1685 update_commandline();
1689 static void handle_key(int key
)
1692 if (input_mode
== NORMAL_MODE
) {
1693 normal_mode_key(key
);
1694 } else if (input_mode
== COMMAND_MODE
) {
1695 command_mode_key(key
);
1696 update_commandline();
1697 } else if (input_mode
== SEARCH_MODE
) {
1698 search_mode_key(key
);
1699 update_commandline();
1703 static void u_getch(void)
1707 int mask
= (1 << 7);
1711 if (key
== ERR
|| key
== 0)
1719 ch
= (unsigned char)key
;
1720 while (bit
> 0 && ch
& mask
) {
1730 u
= ch
& ((1 << bit
) - 1);
1734 if (key
== ERR
|| key
== 0)
1737 ch
= (unsigned char)key
;
1738 u
= (u
<< 6) | (ch
& 63);
1745 static void main_loop(void)
1749 fd_high
= remote_socket
;
1758 FD_SET(remote_socket
, &set
);
1760 /* Timeout must be so small that screen updates seem instant.
1761 * Only affects changes done in other threads (worker, player).
1763 * Too small timeout makes window updates too fast (wastes CPU).
1765 * Too large timeout makes status line (position) updates too slow.
1766 * The timeout is accuracy of player position.
1770 rc
= select(fd_high
+ 1, &set
, NULL
, NULL
, &tv
);
1772 if (ctrl_c_pressed
) {
1779 if (FD_ISSET(remote_socket
, &set
)) {
1780 /* no error msgs for cmus-remote */
1783 remote_server_serve();
1785 if (FD_ISSET(0, &set
)) {
1786 /* diplay errors for interactive commands */
1794 if (key
!= ERR
&& key
!= 0) {
1806 static int get_next(char **filename
)
1808 struct track_info
*info
;
1810 info
= play_queue_remove();
1813 info
= lib_set_next();
1815 info
= pl_set_next();
1821 *filename
= xstrdup(info
->filename
);
1822 track_info_unref(info
);
1826 static const struct player_callbacks player_callbacks
= {
1827 .get_next
= get_next
1830 static void init_curses(void)
1832 struct sigaction act
;
1834 sigemptyset(&act
.sa_mask
);
1836 act
.sa_handler
= sig_int
;
1837 sigaction(SIGINT
, &act
, NULL
);
1839 sigemptyset(&act
.sa_mask
);
1841 act
.sa_handler
= sig_winch
;
1842 sigaction(SIGWINCH
, &act
, NULL
);
1846 /* turn off kb buffering */
1849 keypad(stdscr
, TRUE
);
1851 /* wait max 5 * 0.1 s if there are no keys available
1852 * doesn't really matter because we use select()
1859 use_default_colors();
1861 d_print("Number of supported colors: %d\n", COLORS
);
1864 /* this was disabled while initializing because it needs to be
1865 * called only once after all colors have been set
1870 static void init_all(void)
1874 term
= getenv("TERM");
1875 if (term
&& (strncmp(term
, "xterm", 5) == 0 ||
1876 strncmp(term
, "rxvt", 4) == 0 ||
1877 strcmp(term
, "screen") == 0))
1878 update_window_title
= 1;
1880 remote_socket
= remote_server_init(server_address
);
1882 /* does not select output plugin */
1883 player_init(&player_callbacks
);
1885 /* plugins have been loaded so we know what plugin options are available */
1889 searchable
= tree_searchable
;
1898 /* almost everything must be initialized now */
1901 /* options have been loaded, init plugins (set their options) */
1902 player_init_plugins();
1904 /* finally we can set the output plugin */
1905 player_set_op(output_plugin
);
1907 player_get_volume(&player_info
.vol_left
, &player_info
.vol_right
, &player_info
.vol_max
);
1909 lib_autosave_filename
= xstrjoin(cmus_config_dir
, "/lib.pl");
1910 pl_autosave_filename
= xstrjoin(cmus_config_dir
, "/playlist.pl");
1911 cmus_add(lib_add_track
, lib_autosave_filename
, FILE_TYPE_PL
, JOB_TYPE_LIB
);
1912 cmus_add(pl_add_track
, pl_autosave_filename
, FILE_TYPE_PL
, JOB_TYPE_PL
);
1917 warn("Press <enter> to continue.");
1918 fgets(buf
, sizeof(buf
), stdin
);
1924 static void exit_all(void)
1930 remote_server_exit();
1932 cmus_save(lib_for_each
, lib_autosave_filename
);
1933 cmus_save(pl_for_each
, pl_autosave_filename
);
1951 static struct option options
[NR_FLAGS
+ 1] = {
1953 { 0, "plugins", 0 },
1955 { 0, "version", 0 },
1959 static const char *usage
=
1960 "Usage: %s [OPTION]...\n"
1961 "Curses based music player.\n"
1963 " --listen ADDR listen ADDR (unix socket) instead of /tmp/cmus-$USER\n"
1964 " --plugins list available plugins and exit\n"
1965 " --help display this help and exit\n"
1966 " --version " VERSION
"\n"
1968 "Use cmus-remote to control cmus from command line.\n"
1969 "Documentation: " DATADIR
"/doc/" PACKAGE
"/cmus.html\n"
1970 "Report bugs to <" PACKAGE_BUGREPORT
">.\n";
1972 int main(int argc
, char *argv
[])
1974 int list_plugins
= 0;
1976 program_name
= argv
[0];
1982 idx
= get_option(&argv
, options
, &arg
);
1988 printf(usage
, program_name
);
1991 printf(PACKAGE
" " VERSION
"\nCopyright 2004-2005 Timo Hirvonen\n");
1997 server_address
= xstrdup(arg
);
2002 setlocale(LC_CTYPE
, "");
2004 charset
= nl_langinfo(CODESET
);
2006 charset
= "ISO-8859-1";
2008 if (strcmp(charset
, "UTF-8") == 0) {
2014 if (server_address
== NULL
) {
2015 server_address
= xnew(char, 256);
2016 snprintf(server_address
, 256, "/tmp/cmus-%s", user_name
);
2019 d_print("charset = '%s'\n", charset
);
2021 player_load_plugins();
2023 player_dump_plugins();