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>
44 #include "config/version.h"
50 #include <sys/ioctl.h>
67 /* defined in <term.h> but without const */
68 char *tgetstr(const char *id
, char **area
);
69 char *tgoto(const char *cap
, int col
, int row
);
71 /* globals. documented in ui_curses.h */
73 int ui_initialized
= 0;
74 enum ui_input_mode input_mode
= NORMAL_MODE
;
75 int cur_view
= TREE_VIEW
;
76 struct searchable
*searchable
;
78 /* display parse errors? (command line) */
79 int display_errors
= 0;
81 char *lib_filename
= NULL
;
82 char *pl_filename
= NULL
;
84 /* ------------------------------------------------------------------------- */
86 /* currently playing file */
87 static struct track_info
*cur_track_info
= NULL
;
89 static int running
= 1;
90 static char *lib_autosave_filename
;
91 static char *pl_autosave_filename
;
93 /* shown error message and time stamp
94 * error is cleared if it is older than 3s and key was pressed
96 static char error_buf
[512];
97 static time_t error_time
= 0;
98 /* info messages are displayed in different color */
99 static int msg_is_error
;
100 static int error_count
= 0;
102 static char *server_address
= NULL
;
103 static int remote_socket
= -1;
105 static char *charset
= NULL
;
106 static char print_buffer
[512];
108 /* destination buffer for utf8_encode and utf8_decode */
109 static char conv_buffer
[512];
111 #define print_buffer_size (sizeof(print_buffer) - 1)
112 static int using_utf8
;
114 static const char *t_ts
;
115 static const char *t_fs
;
117 static int tree_win_x
= 0;
118 static int tree_win_y
= 0;
119 static int tree_win_w
= 0;
121 static int track_win_x
= 0;
122 static int track_win_y
= 0;
123 static int track_win_w
= 0;
132 CURSED_WIN_ACTIVE_CUR
,
133 CURSED_WIN_ACTIVE_SEL
,
134 CURSED_WIN_ACTIVE_SEL_CUR
,
149 static unsigned char cursed_to_bg_idx
[NR_CURSED
] = {
152 COLOR_WIN_INACTIVE_SEL_BG
,
153 COLOR_WIN_INACTIVE_CUR_SEL_BG
,
158 COLOR_WIN_CUR_SEL_BG
,
171 static unsigned char cursed_to_fg_idx
[NR_CURSED
] = {
174 COLOR_WIN_INACTIVE_SEL_FG
,
175 COLOR_WIN_INACTIVE_CUR_SEL_FG
,
180 COLOR_WIN_CUR_SEL_FG
,
193 /* index is CURSED_*, value is fucking color pair */
194 static int pairs
[NR_CURSED
];
210 static struct format_option track_fopts
[NR_TFS
+ 1] = {
240 static struct format_option status_fopts
[NR_SFS
+ 1] = {
256 static void utf8_encode(const char *buffer
)
258 static iconv_t cd
= (iconv_t
)-1;
264 if (cd
== (iconv_t
)-1) {
265 d_print("iconv_open(UTF-8, %s)\n", charset
);
266 cd
= iconv_open("UTF-8", charset
);
267 if (cd
== (iconv_t
)-1) {
268 d_print("iconv_open failed: %s\n", strerror(errno
));
275 os
= sizeof(conv_buffer
) - 1;
276 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
279 d_print("iconv failed: %s\n", strerror(errno
));
284 static void utf8_decode(const char *buffer
)
286 static iconv_t cd
= (iconv_t
)-1;
292 if (cd
== (iconv_t
)-1) {
293 d_print("iconv_open(%s, UTF-8)\n", charset
);
294 cd
= iconv_open(charset
, "UTF-8");
295 if (cd
== (iconv_t
)-1) {
296 d_print("iconv_open failed: %s\n", strerror(errno
));
303 os
= sizeof(conv_buffer
) - 1;
304 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
307 d_print("iconv failed: %s\n", strerror(errno
));
312 /* screen updates {{{ */
314 static void dump_print_buffer(int row
, int col
)
317 mvaddstr(row
, col
, print_buffer
);
319 utf8_decode(print_buffer
);
320 mvaddstr(row
, col
, conv_buffer
);
324 static void sprint(int row
, int col
, const char *str
, int width
, int indent
)
326 int s
, d
, ellipsis_pos
= 0, cut_double_width
= 0;
330 memset(print_buffer
, ' ', d
);
339 u_get_char(str
, &s
, &u
);
341 memset(print_buffer
+ d
, ' ', width
);
349 if (width
== 4 && w
== 2) {
350 /* can't cut double-width char */
351 ellipsis_pos
= d
+ 1;
352 cut_double_width
= 1;
359 if (cut_double_width
) {
360 /* first half of the double-width char */
361 print_buffer
[d
- 1] = ' ';
363 print_buffer
[d
++] = '.';
364 print_buffer
[d
++] = '.';
365 print_buffer
[d
++] = '.';
368 u_set_char(print_buffer
, &d
, u
);
370 print_buffer
[d
++] = ' ';
371 print_buffer
[d
++] = 0;
372 dump_print_buffer(row
, col
);
375 static void sprint_ascii(int row
, int col
, const char *str
, int len
)
382 print_buffer
[0] = ' ';
384 memcpy(print_buffer
+ 1, str
, len
- 3);
385 print_buffer
[len
- 2] = '.';
386 print_buffer
[len
- 1] = '.';
387 print_buffer
[len
- 0] = '.';
389 memcpy(print_buffer
+ 1, str
, l
);
390 memset(print_buffer
+ 1 + l
, ' ', len
- l
);
392 print_buffer
[len
+ 1] = ' ';
393 print_buffer
[len
+ 2] = 0;
394 mvaddstr(row
, col
, print_buffer
);
397 static void print_tree(struct window
*win
, int row
, struct iter
*iter
)
399 const char *noname
= "<no name>";
400 struct artist
*artist
;
403 int current
, selected
, active
;
405 artist
= iter_to_artist(iter
);
406 album
= iter_to_album(iter
);
410 current
= CUR_ALBUM
== album
;
412 current
= CUR_ARTIST
== artist
;
415 window_get_sel(win
, &sel
);
416 selected
= iters_equal(iter
, &sel
);
417 active
= lib_cur_win
== lib_tree_win
;
418 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
420 sprint(tree_win_y
+ row
+ 1, tree_win_x
, album
->name
? : noname
, tree_win_w
, 2);
422 sprint(tree_win_y
+ row
+ 1, tree_win_x
, artist
->name
? : noname
, tree_win_w
, 0);
426 static inline void fopt_set_str(struct format_option
*fopt
, const char *str
)
428 BUG_ON(fopt
->type
!= FO_STR
);
437 static inline void fopt_set_int(struct format_option
*fopt
, int value
, int empty
)
439 BUG_ON(fopt
->type
!= FO_INT
);
440 fopt
->fo_int
= value
;
444 static inline void fopt_set_time(struct format_option
*fopt
, int value
, int empty
)
446 BUG_ON(fopt
->type
!= FO_TIME
);
447 fopt
->fo_time
= value
;
451 static void fill_track_fopts(struct tree_track
*track
)
453 const char *filename
;
454 const struct track_info
*ti
= tree_track_info(track
);
458 filename
= ti
->filename
;
460 utf8_encode(ti
->filename
);
461 filename
= conv_buffer
;
463 disc
= track
->shuffle_track
.simple_track
.disc
;
464 num
= track
->shuffle_track
.simple_track
.num
;
466 fopt_set_str(&track_fopts
[TF_ARTIST
], track
->album
->artist
->name
);
467 fopt_set_str(&track_fopts
[TF_ALBUM
], track
->album
->name
);
468 fopt_set_int(&track_fopts
[TF_DISC
], disc
, disc
== -1);
469 fopt_set_int(&track_fopts
[TF_TRACK
], num
, num
== -1);
470 fopt_set_str(&track_fopts
[TF_TITLE
], comments_get_val(ti
->comments
, "title"));
471 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(ti
->comments
, "date"));
472 fopt_set_str(&track_fopts
[TF_GENRE
], comments_get_val(ti
->comments
, "genre"));
473 fopt_set_time(&track_fopts
[TF_DURATION
], ti
->duration
, ti
->duration
== -1);
474 fopt_set_str(&track_fopts
[TF_PATHFILE
], filename
);
475 if (is_url(ti
->filename
)) {
476 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
480 f
= strrchr(filename
, '/');
482 fopt_set_str(&track_fopts
[TF_FILE
], f
+ 1);
484 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
489 static void fill_track_fopts_track_info(struct track_info
*info
)
495 filename
= info
->filename
;
497 utf8_encode(info
->filename
);
498 filename
= conv_buffer
;
500 disc
= comments_get_int(info
->comments
, "discnumber");
501 num
= comments_get_int(info
->comments
, "tracknumber");
503 fopt_set_str(&track_fopts
[TF_ARTIST
], comments_get_val(info
->comments
, "artist"));
504 fopt_set_str(&track_fopts
[TF_ALBUM
], comments_get_val(info
->comments
, "album"));
505 fopt_set_int(&track_fopts
[TF_DISC
], disc
, disc
== -1);
506 fopt_set_int(&track_fopts
[TF_TRACK
], num
, num
== -1);
507 fopt_set_str(&track_fopts
[TF_TITLE
], comments_get_val(info
->comments
, "title"));
508 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(info
->comments
, "date"));
509 fopt_set_str(&track_fopts
[TF_GENRE
], comments_get_val(info
->comments
, "genre"));
510 fopt_set_time(&track_fopts
[TF_DURATION
], info
->duration
, info
->duration
== -1);
511 fopt_set_str(&track_fopts
[TF_PATHFILE
], filename
);
512 if (is_url(info
->filename
)) {
513 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
517 f
= strrchr(filename
, '/');
519 fopt_set_str(&track_fopts
[TF_FILE
], f
+ 1);
521 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
526 static void print_track(struct window
*win
, int row
, struct iter
*iter
)
528 struct tree_track
*track
;
530 int current
, selected
, active
;
532 track
= iter_to_tree_track(iter
);
533 current
= lib_cur_track
== track
;
534 window_get_sel(win
, &sel
);
535 selected
= iters_equal(iter
, &sel
);
536 active
= lib_cur_win
== lib_track_win
;
537 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
539 fill_track_fopts(track
);
541 if (track_info_has_tag(tree_track_info(track
))) {
542 format_print(print_buffer
, track_win_w
, track_win_format
, track_fopts
);
544 format_print(print_buffer
, track_win_w
, track_win_alt_format
, track_fopts
);
546 dump_print_buffer(track_win_y
+ row
+ 1, track_win_x
);
549 /* used by print_editable only */
550 static struct simple_track
*current_track
;
552 static void print_editable(struct window
*win
, int row
, struct iter
*iter
)
554 struct simple_track
*track
;
556 int current
, selected
, active
;
558 track
= iter_to_simple_track(iter
);
559 current
= current_track
== track
;
560 window_get_sel(win
, &sel
);
561 selected
= iters_equal(iter
, &sel
);
564 if (!selected
&& track
->marked
) {
569 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
571 fill_track_fopts_track_info(track
->info
);
573 if (track_info_has_tag(track
->info
)) {
574 format_print(print_buffer
, COLS
, list_win_format
, track_fopts
);
576 format_print(print_buffer
, COLS
, list_win_alt_format
, track_fopts
);
578 dump_print_buffer(row
+ 1, 0);
581 static void print_browser(struct window
*win
, int row
, struct iter
*iter
)
583 struct browser_entry
*e
;
587 e
= iter_to_browser_entry(iter
);
588 window_get_sel(win
, &sel
);
589 selected
= iters_equal(iter
, &sel
);
594 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
596 if (e
->type
== BROWSER_ENTRY_DIR
) {
597 bkgdset(pairs
[CURSED_DIR
]);
599 bkgdset(pairs
[CURSED_WIN
]);
603 /* file name encoding == terminal encoding. no need to convert */
605 sprint(row
+ 1, 0, e
->name
, COLS
, 0);
607 sprint_ascii(row
+ 1, 0, e
->name
, COLS
);
611 static void print_filter(struct window
*win
, int row
, struct iter
*iter
)
614 struct filter_entry
*e
= iter_to_filter_entry(iter
);
620 /* is the filter currently active? */
621 int current
= e
->active
;
623 window_get_sel(win
, &sel
);
624 selected
= iters_equal(iter
, &sel
);
625 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
627 snprintf(buf
, sizeof(buf
), "%c %-15s %s", e
->selected
? '*' : ' ', e
->name
, e
->filter
);
628 sprint(row
+ 1, 0, buf
, COLS
, 0);
631 static void update_window(struct window
*win
, int x
, int y
, int w
, const char *title
,
632 void (*print
)(struct window
*, int, struct iter
*))
640 bkgdset(pairs
[CURSED_WIN_TITLE
]);
641 c
= snprintf(print_buffer
, w
+ 1, " %s", title
);
644 memset(print_buffer
+ c
, ' ', w
- c
+ 1);
646 dump_print_buffer(y
, x
);
647 nr_rows
= window_get_nr_rows(win
);
649 if (window_get_top(win
, &iter
)) {
650 while (i
< nr_rows
) {
651 print(win
, i
, &iter
);
653 if (!window_get_next(win
, &iter
))
659 memset(print_buffer
, ' ', w
);
661 while (i
< nr_rows
) {
662 dump_print_buffer(y
+ i
+ 1, x
);
667 static void update_tree_window(void)
669 update_window(lib_tree_win
, tree_win_x
, tree_win_y
,
670 tree_win_w
, "Artist / Album", print_tree
);
673 static void update_track_window(void)
677 /* it doesn't matter what format options we use because the format
678 * string does not contain any format charaters */
679 format_print(title
, track_win_w
- 2, "Track%=Library", track_fopts
);
680 update_window(lib_track_win
, track_win_x
, track_win_y
,
681 track_win_w
, title
, print_track
);
684 static const char *pretty(const char *path
)
686 static int home_len
= -1;
687 static char buf
[256];
690 home_len
= strlen(home_dir
);
692 if (strncmp(path
, home_dir
, home_len
) || path
[home_len
] != '/')
696 strcpy(buf
+ 1, path
+ home_len
);
700 static const char * const sorted_names
[2] = { "", "sorted by " };
702 static void update_editable_window(struct editable
*e
, const char *title
, const char *filename
)
711 utf8_encode(filename
);
712 filename
= conv_buffer
;
714 snprintf(buf
, sizeof(buf
), "%s %s - %d tracks", title
,
715 pretty(filename
), e
->nr_tracks
);
717 snprintf(buf
, sizeof(buf
), "%s - %d tracks", title
, e
->nr_tracks
);
722 snprintf(buf
+ pos
, sizeof(buf
) - pos
, " (%d marked)", e
->nr_marked
);
725 snprintf(buf
+ pos
, sizeof(buf
) - pos
, " %s%s",
726 sorted_names
[e
->sort_str
[0] != 0], e
->sort_str
);
728 update_window(e
->win
, 0, 0, COLS
, buf
, &print_editable
);
731 static void update_sorted_window(void)
733 current_track
= (struct simple_track
*)lib_cur_track
;
734 update_editable_window(&lib_editable
, "Library", lib_filename
);
737 static void update_pl_window(void)
739 current_track
= pl_cur_track
;
740 update_editable_window(&pl_editable
, "Playlist", pl_filename
);
743 static void update_play_queue_window(void)
745 current_track
= NULL
;
746 update_editable_window(&pq_editable
, "Play Queue", NULL
);
749 static void update_browser_window(void)
756 dirname
= browser_dir
;
758 utf8_encode(browser_dir
);
759 dirname
= conv_buffer
;
761 snprintf(title
, sizeof(title
), "Browser - %s", dirname
);
762 update_window(browser_win
, 0, 0, COLS
, title
, print_browser
);
765 static void update_filters_window(void)
767 update_window(filters_win
, 0, 0, COLS
, "Library Filters", print_filter
);
770 static void draw_separator(void)
774 bkgdset(pairs
[CURSED_WIN_TITLE
]);
775 mvaddch(0, tree_win_w
, ' ');
776 bkgdset(pairs
[CURSED_SEPARATOR
]);
777 for (row
= 1; row
< LINES
- 3; row
++)
778 mvaddch(row
, tree_win_w
, ACS_VLINE
);
781 static void do_update_view(int full
)
786 if (full
|| lib_tree_win
->changed
)
787 update_tree_window();
788 if (full
|| lib_track_win
->changed
)
789 update_track_window();
795 update_sorted_window();
805 update_play_queue_window();
809 update_browser_window();
812 update_filters_window();
817 static void do_update_statusline(void)
819 static const char *status_strs
[] = { ".", ">", "|" };
820 static const char *cont_strs
[] = { " ", "C" };
821 static const char *repeat_strs
[] = { " ", "R" };
822 static const char *shuffle_strs
[] = { " ", "S" };
823 int buffer_fill
, vol
, vol_left
, vol_right
;
829 fopt_set_time(&status_fopts
[SF_TOTAL
], play_library
? lib_editable
.total_time
:
830 pl_editable
.total_time
, 0);
833 fopt_set_str(&status_fopts
[SF_REPEAT
], repeat_strs
[repeat
]);
834 fopt_set_str(&status_fopts
[SF_SHUFFLE
], shuffle_strs
[shuffle
]);
835 fopt_set_str(&status_fopts
[SF_PLAYLISTMODE
], aaa_mode_names
[aaa_mode
]);
838 duration
= cur_track_info
->duration
;
842 if (volume_max
== 0) {
843 vol_left
= vol_right
= vol
= -1;
845 vol_left
= scale_to_percentage(player_info
.vol_left
, volume_max
);
846 vol_right
= scale_to_percentage(player_info
.vol_right
, volume_max
);
847 vol
= (vol_left
+ vol_right
+ 1) / 2;
849 buffer_fill
= scale_to_percentage(player_info
.buffer_fill
, player_info
.buffer_size
);
851 fopt_set_str(&status_fopts
[SF_STATUS
], status_strs
[player_info
.status
]);
853 if (show_remaining_time
&& duration
!= -1) {
854 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
- duration
, 0);
856 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
, 0);
859 fopt_set_time(&status_fopts
[SF_DURATION
], duration
, 0);
860 fopt_set_int(&status_fopts
[SF_VOLUME
], vol
, 0);
861 fopt_set_int(&status_fopts
[SF_LVOLUME
], vol_left
, 0);
862 fopt_set_int(&status_fopts
[SF_RVOLUME
], vol_right
, 0);
863 fopt_set_int(&status_fopts
[SF_BUFFER
], buffer_fill
, 0);
864 fopt_set_str(&status_fopts
[SF_CONTINUE
], cont_strs
[player_cont
]);
866 strcpy(format
, " %s %p ");
868 strcat(format
, "/ %d ");
869 strcat(format
, "- %t ");
870 if (volume_max
!= 0) {
871 if (player_info
.vol_left
!= player_info
.vol_right
) {
872 strcat(format
, "vol: %l,%r ");
874 strcat(format
, "vol: %v ");
877 if (cur_track_info
&& is_url(cur_track_info
->filename
))
878 strcat(format
, "buf: %b ");
879 strcat(format
, "%=");
881 /* artist/album modes work only in lib */
883 /* shuffle overrides sorted mode */
884 strcat(format
, "%L from library");
885 } else if (play_sorted
) {
886 strcat(format
, "%L from sorted library");
888 strcat(format
, "%L from library");
891 strcat(format
, "playlist");
893 strcat(format
, " | %1C%1R%1S ");
894 format_print(print_buffer
, COLS
, format
, status_fopts
);
896 msg
= player_info
.error_msg
;
897 player_info
.error_msg
= NULL
;
899 player_info_unlock();
901 bkgdset(pairs
[CURSED_STATUSLINE
]);
902 dump_print_buffer(LINES
- 2, 0);
905 error_msg("%s", msg
);
910 static void dump_buffer(const char *buffer
)
920 static void do_update_commandline(void)
929 bkgdset(pairs
[CURSED_ERROR
]);
931 bkgdset(pairs
[CURSED_INFO
]);
937 bkgdset(pairs
[CURSED_COMMANDLINE
]);
938 if (input_mode
== NORMAL_MODE
) {
945 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
946 * characters are invalid UTF-8 so it really is in locale's
949 * This code should be safe because cmdline.bpos ==
950 * cmdline.cpos as every non-ASCII character is counted as one
951 * invalid UTF-8 byte.
953 * NOTE: This has nothing to do with widths of printed
954 * characters. I.e. even if there were control characters
955 * (displayed as <xx>) there would be no problem because bpos
956 * still equals to cpos, I think.
958 utf8_encode(cmdline
.line
);
962 /* COMMAND_MODE or SEARCH_MODE */
963 w
= u_str_width(str
);
965 if (input_mode
== SEARCH_MODE
)
966 ch
= search_direction
== SEARCH_FORWARD
? '/' : '?';
970 idx
= u_copy_chars(print_buffer
, str
, &w
);
971 print_buffer
[idx
] = 0;
972 dump_buffer(print_buffer
);
975 /* keep cursor as far right as possible */
978 /* cursor pos (width, not chars. doesn't count the ':') */
979 cw
= u_str_nwidth(str
, cmdline
.cpos
);
981 skip
= cw
+ 2 - COLS
;
986 /* skip rest (if any) */
987 idx
= u_skip_chars(str
, &skip
);
990 idx
= u_copy_chars(print_buffer
, str
+ idx
, &width
);
991 while (width
< COLS
) {
992 /* cursor is at end of the buffer
993 * print 1, 2 or 3 spaces
997 * If the last _skipped_ character was double-width we may need
1000 * If the last _skipped_ character was invalid UTF-8 we may need
1001 * to print 3 spaces.
1003 print_buffer
[idx
++] = ' ';
1006 print_buffer
[idx
] = 0;
1007 dump_buffer(print_buffer
);
1009 /* print ':' + COLS - 1 chars */
1012 idx
= u_copy_chars(print_buffer
, str
, &width
);
1013 print_buffer
[idx
] = 0;
1014 dump_buffer(print_buffer
);
1019 /* lock player_info! */
1020 static const char *get_stream_title(void)
1022 static char stream_title
[255 * 16 + 1];
1025 ptr
= strstr(player_info
.metadata
, "StreamTitle='");
1031 if (*ptr
== '\'' && *(ptr
+ 1) == ';') {
1032 memcpy(stream_title
, title
, ptr
- title
);
1033 stream_title
[ptr
- title
] = 0;
1034 return stream_title
;
1041 static void set_title(const char *title
)
1044 printf("%s%s%s", tgoto(t_ts
, 0, 0), title
, t_fs
);
1049 static void do_update_titleline(void)
1051 bkgdset(pairs
[CURSED_TITLELINE
]);
1053 if (cur_track_info
) {
1054 int i
, use_alt_format
= 0;
1057 fill_track_fopts_track_info(cur_track_info
);
1058 if (is_url(cur_track_info
->filename
)) {
1059 const char *title
= get_stream_title();
1063 fopt_set_str(&track_fopts
[TF_TITLE
], title
);
1065 use_alt_format
= !track_info_has_tag(cur_track_info
);
1068 if (use_alt_format
) {
1069 format_print(print_buffer
, COLS
, current_alt_format
, track_fopts
);
1071 format_print(print_buffer
, COLS
, current_format
, track_fopts
);
1073 dump_print_buffer(LINES
- 3, 0);
1075 /* set window title */
1076 if (use_alt_format
) {
1077 format_print(print_buffer
, sizeof(print_buffer
) - 1,
1078 window_title_alt_format
, track_fopts
);
1080 format_print(print_buffer
, sizeof(print_buffer
) - 1,
1081 window_title_format
, track_fopts
);
1084 /* remove whitespace */
1085 i
= strlen(print_buffer
) - 1;
1086 while (i
> 0 && print_buffer
[i
] == ' ')
1088 print_buffer
[i
+ 1] = 0;
1091 wtitle
= print_buffer
;
1093 utf8_decode(print_buffer
);
1094 wtitle
= conv_buffer
;
1102 set_title("cmus " VERSION
);
1104 player_info_unlock();
1107 static int cmdline_cursor_column(void)
1114 /* see do_update_commandline */
1115 utf8_encode(cmdline
.line
);
1119 /* width of the text in the buffer before cursor */
1120 cw
= u_str_nwidth(str
, cmdline
.cpos
);
1122 if (1 + cw
< COLS
) {
1123 /* whole line is visible */
1127 /* beginning of cmdline is not visible */
1129 /* check if the first visible char in cmdline would be halved
1130 * double-width character (or invalid byte <xx>) which is not possible.
1131 * we need to skip the whole character and move cursor to COLS - 2
1133 skip
= cw
+ 2 - COLS
;
1140 u_skip_chars(str
, &s
);
1142 /* the last skipped char was double-width or <xx> */
1143 return COLS
- 1 - (s
- skip
);
1148 static void post_update(void)
1150 /* refresh makes cursor visible at least for urxvt */
1151 if (input_mode
== COMMAND_MODE
|| input_mode
== SEARCH_MODE
) {
1152 move(LINES
- 1, cmdline_cursor_column());
1162 void update_titleline(void)
1165 do_update_titleline();
1169 void update_full(void)
1171 if (!ui_initialized
)
1177 do_update_titleline();
1178 do_update_statusline();
1179 do_update_commandline();
1184 static void update_commandline(void)
1187 do_update_commandline();
1191 void update_statusline(void)
1193 if (!ui_initialized
)
1197 do_update_statusline();
1201 void info_msg(const char *format
, ...)
1205 va_start(ap
, format
);
1206 vsnprintf(error_buf
, sizeof(error_buf
), format
, ap
);
1211 update_commandline();
1214 void error_msg(const char *format
, ...)
1218 if (!display_errors
)
1221 strcpy(error_buf
, "Error: ");
1222 va_start(ap
, format
);
1223 vsnprintf(error_buf
+ 7, sizeof(error_buf
) - 7, format
, ap
);
1229 if (ui_initialized
) {
1230 error_time
= time(NULL
);
1231 update_commandline();
1233 warn("%s\n", error_buf
);
1238 int yes_no_query(const char *format
, ...)
1244 va_start(ap
, format
);
1245 vsnprintf(buffer
, sizeof(buffer
), format
, ap
);
1249 bkgdset(pairs
[CURSED_INFO
]);
1251 /* no need to convert buffer.
1252 * it is always encoded in the right charset (assuming filenames are
1253 * encoded in same charset as LC_CTYPE).
1263 if (ch
== ERR
|| ch
== 0)
1269 update_commandline();
1273 void search_not_found(void)
1275 const char *what
= "Track";
1277 if (search_restricted
) {
1280 what
= "Artist/album";
1288 what
= "File/Directory";
1303 what
= "File/Directory";
1310 info_msg("%s not found: %s", what
, search_str
? : "");
1313 void set_view(int view
)
1315 if (view
== cur_view
)
1322 searchable
= tree_searchable
;
1323 update_tree_window();
1324 update_track_window();
1328 searchable
= lib_editable
.searchable
;
1329 update_sorted_window();
1332 searchable
= pl_editable
.searchable
;
1336 searchable
= pq_editable
.searchable
;
1337 update_play_queue_window();
1340 searchable
= browser_searchable
;
1341 update_browser_window();
1344 searchable
= filters_searchable
;
1345 update_filters_window();
1353 void enter_command_mode(void)
1357 input_mode
= COMMAND_MODE
;
1358 update_commandline();
1361 void enter_search_mode(void)
1365 input_mode
= SEARCH_MODE
;
1366 search_direction
= SEARCH_FORWARD
;
1367 update_commandline();
1370 void enter_search_backward_mode(void)
1374 input_mode
= SEARCH_MODE
;
1375 search_direction
= SEARCH_BACKWARD
;
1376 update_commandline();
1384 void update_colors(void)
1388 if (!ui_initialized
)
1391 for (i
= 0; i
< NR_CURSED
; i
++) {
1392 int bg
= colors
[cursed_to_bg_idx
[i
]];
1393 int fg
= colors
[cursed_to_fg_idx
[i
]];
1396 if (fg
>= 8 && fg
<= 15) {
1397 /* fg colors 8..15 are special (0..7 + bold) */
1398 init_pair(pair
, fg
& 7, bg
);
1399 pairs
[i
] = COLOR_PAIR(pair
) | (fg
& BRIGHT
? A_BOLD
: 0);
1401 init_pair(pair
, fg
, bg
);
1402 pairs
[i
] = COLOR_PAIR(pair
);
1407 static void clear_error(void)
1409 time_t t
= time(NULL
);
1411 /* error msg is visible at least 3s */
1412 if (t
- error_time
< 3)
1418 update_commandline();
1422 /* screen updates }}} */
1424 static void spawn_status_program(void)
1426 static const char *status_strs
[] = { "stopped", "playing", "paused" };
1427 const char *stream_title
= NULL
;
1431 if (status_display_program
== NULL
|| status_display_program
[0] == 0)
1435 status
= player_info
.status
;
1437 stream_title
= get_stream_title();
1438 player_info_unlock();
1441 argv
[i
++] = xstrdup(status_display_program
);
1443 argv
[i
++] = xstrdup("status");
1444 argv
[i
++] = xstrdup(status_strs
[status
]);
1445 if (cur_track_info
) {
1446 static const char *keys
[] = {
1447 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1451 if (is_url(cur_track_info
->filename
)) {
1452 argv
[i
++] = xstrdup("url");
1453 argv
[i
++] = xstrdup(cur_track_info
->filename
);
1455 argv
[i
++] = xstrdup("title");
1456 argv
[i
++] = xstrdup(stream_title
);
1461 argv
[i
++] = xstrdup("file");
1462 argv
[i
++] = xstrdup(cur_track_info
->filename
);
1463 for (j
= 0; keys
[j
]; j
++) {
1464 const char *key
= keys
[j
];
1467 val
= comments_get_val(cur_track_info
->comments
, key
);
1469 argv
[i
++] = xstrdup(key
);
1470 argv
[i
++] = xstrdup(val
);
1473 snprintf(buf
, sizeof(buf
), "%d", cur_track_info
->duration
);
1474 argv
[i
++] = xstrdup("duration");
1475 argv
[i
++] = xstrdup(buf
);
1479 if (spawn(argv
, &status
) == -1)
1480 error_msg("couldn't run `%s': %s", status_display_program
, strerror(errno
));
1481 for (i
= 0; argv
[i
]; i
++)
1485 static int ctrl_c_pressed
= 0;
1487 static void sig_int(int sig
)
1492 static int needs_to_resize
= 1;
1494 static void sig_winch(int sig
)
1496 needs_to_resize
= 1;
1499 static int get_window_size(int *lines
, int *columns
)
1503 if (ioctl(0, TIOCGWINSZ
, &ws
) == -1)
1505 *columns
= ws
.ws_col
;
1510 static void resize_tree_view(int w
, int h
)
1513 track_win_w
= w
- tree_win_w
- 1;
1516 if (track_win_w
< 8)
1520 track_win_x
= tree_win_w
+ 1;
1524 window_set_nr_rows(lib_tree_win
, h
);
1525 window_set_nr_rows(lib_track_win
, h
);
1528 static void update(void)
1530 int needs_view_update
= 0;
1531 int needs_title_update
= 0;
1532 int needs_status_update
= 0;
1533 int needs_command_update
= 0;
1534 int needs_spawn
= 0;
1536 if (needs_to_resize
) {
1540 if (get_window_size(&lines
, &columns
) == 0) {
1541 needs_to_resize
= 0;
1542 resizeterm(lines
, columns
);
1550 resize_tree_view(w
, h
);
1551 window_set_nr_rows(lib_editable
.win
, h
- 1);
1552 window_set_nr_rows(pl_editable
.win
, h
- 1);
1553 window_set_nr_rows(pq_editable
.win
, h
- 1);
1554 window_set_nr_rows(filters_win
, h
- 1);
1555 window_set_nr_rows(browser_win
, h
- 1);
1557 needs_title_update
= 1;
1558 needs_status_update
= 1;
1559 needs_command_update
= 1;
1566 needs_spawn
= player_info
.status_changed
|| player_info
.file_changed
||
1567 player_info
.metadata_changed
;
1569 if (player_info
.file_changed
) {
1571 track_info_unref(cur_track_info
);
1572 if (player_info
.filename
[0] == 0) {
1573 cur_track_info
= NULL
;
1575 cur_track_info
= cmus_get_track_info(player_info
.filename
);
1577 player_info
.file_changed
= 0;
1578 needs_title_update
= 1;
1579 needs_status_update
= 1;
1581 if (player_info
.metadata_changed
) {
1582 player_info
.metadata_changed
= 0;
1583 needs_title_update
= 1;
1585 if (player_info
.position_changed
|| player_info
.status_changed
|| player_info
.vol_changed
) {
1586 player_info
.position_changed
= 0;
1587 player_info
.status_changed
= 0;
1588 player_info
.vol_changed
= 0;
1590 needs_status_update
= 1;
1594 needs_view_update
+= lib_tree_win
->changed
|| lib_track_win
->changed
;
1597 needs_view_update
+= lib_editable
.win
->changed
;
1600 needs_view_update
+= pl_editable
.win
->changed
;
1603 needs_view_update
+= pq_editable
.win
->changed
;
1606 needs_view_update
+= browser_win
->changed
;
1609 needs_view_update
+= filters_win
->changed
;
1613 /* total time changed? */
1615 needs_status_update
+= lib_editable
.win
->changed
;
1617 needs_status_update
+= pl_editable
.win
->changed
;
1621 player_info_unlock();
1624 spawn_status_program();
1626 if (needs_view_update
|| needs_title_update
|| needs_status_update
|| needs_command_update
) {
1629 if (needs_view_update
)
1631 if (needs_title_update
)
1632 do_update_titleline();
1633 if (needs_status_update
)
1634 do_update_statusline();
1635 if (needs_command_update
)
1636 do_update_commandline();
1641 static void handle_ch(uchar ch
)
1644 if (input_mode
== NORMAL_MODE
) {
1646 } else if (input_mode
== COMMAND_MODE
) {
1647 command_mode_ch(ch
);
1648 update_commandline();
1649 } else if (input_mode
== SEARCH_MODE
) {
1651 update_commandline();
1655 static void handle_key(int key
)
1658 if (input_mode
== NORMAL_MODE
) {
1659 normal_mode_key(key
);
1660 } else if (input_mode
== COMMAND_MODE
) {
1661 command_mode_key(key
);
1662 update_commandline();
1663 } else if (input_mode
== SEARCH_MODE
) {
1664 search_mode_key(key
);
1665 update_commandline();
1669 static void u_getch(void)
1673 int mask
= (1 << 7);
1677 if (key
== ERR
|| key
== 0)
1685 ch
= (unsigned char)key
;
1686 while (bit
> 0 && ch
& mask
) {
1696 u
= ch
& ((1 << bit
) - 1);
1700 if (key
== ERR
|| key
== 0)
1703 ch
= (unsigned char)key
;
1704 u
= (u
<< 6) | (ch
& 63);
1711 static void main_loop(void)
1715 fd_high
= remote_socket
;
1724 FD_SET(remote_socket
, &set
);
1726 /* Timeout must be so small that screen updates seem instant.
1727 * Only affects changes done in other threads (worker, player).
1729 * Too small timeout makes window updates too fast (wastes CPU).
1731 * Too large timeout makes status line (position) updates too slow.
1732 * The timeout is accuracy of player position.
1736 rc
= select(fd_high
+ 1, &set
, NULL
, NULL
, &tv
);
1738 if (ctrl_c_pressed
) {
1745 if (FD_ISSET(remote_socket
, &set
)) {
1746 /* no error msgs for cmus-remote */
1749 remote_server_serve();
1751 if (FD_ISSET(0, &set
)) {
1752 /* diplay errors for interactive commands */
1760 if (key
!= ERR
&& key
!= 0) {
1767 ch
|= U_INVALID_MASK
;
1776 static int get_next(char **filename
)
1778 struct track_info
*info
;
1781 info
= play_queue_remove();
1784 info
= lib_set_next();
1786 info
= pl_set_next();
1794 *filename
= xstrdup(info
->filename
);
1795 track_info_unref(info
);
1799 static const struct player_callbacks player_callbacks
= {
1800 .get_next
= get_next
1803 static void init_curses(void)
1805 struct sigaction act
;
1808 sigemptyset(&act
.sa_mask
);
1810 act
.sa_handler
= sig_int
;
1811 sigaction(SIGINT
, &act
, NULL
);
1813 sigemptyset(&act
.sa_mask
);
1815 act
.sa_handler
= sig_winch
;
1816 sigaction(SIGWINCH
, &act
, NULL
);
1820 /* turn off kb buffering */
1823 keypad(stdscr
, TRUE
);
1825 /* wait max 5 * 0.1 s if there are no keys available
1826 * doesn't really matter because we use select()
1833 use_default_colors();
1835 d_print("Number of supported colors: %d\n", COLORS
);
1838 /* this was disabled while initializing because it needs to be
1839 * called only once after all colors have been set
1844 t_ts
= tgetstr("ts", &ptr
);
1845 t_fs
= tgetstr("fs", &ptr
);
1846 d_print("ts: %d fs: %d\n", !!t_ts
, !!t_fs
);
1851 if (!t_ts
&& (term
= getenv("TERM"))) {
1856 * terminal (xfce): xterm
1857 * urxvt: rxvt-unicode
1858 * xterm: xterm, xterm-{,16,88,256}color
1860 if (!strcmp(term
, "screen")) {
1863 } else if (!strncmp(term
, "xterm", 5) ||
1864 !strncmp(term
, "rxvt", 4) ||
1865 !strcmp(term
, "Eterm")) {
1866 /* \033]1; change icon
1867 * \033]2; change title
1868 * \033]0; change both
1876 static void init_all(void)
1878 remote_socket
= remote_server_init(server_address
);
1880 /* does not select output plugin */
1881 player_init(&player_callbacks
);
1883 /* plugins have been loaded so we know what plugin options are available */
1887 searchable
= tree_searchable
;
1896 /* almost everything must be initialized now */
1899 /* finally we can set the output plugin */
1900 player_set_op(output_plugin
);
1902 player_get_volume(&player_info
.vol_left
, &player_info
.vol_right
);
1904 lib_autosave_filename
= xstrjoin(cmus_config_dir
, "/lib.pl");
1905 pl_autosave_filename
= xstrjoin(cmus_config_dir
, "/playlist.pl");
1906 pl_filename
= xstrdup(pl_autosave_filename
);
1907 lib_filename
= xstrdup(lib_autosave_filename
);
1909 cmus_add(lib_add_track
, lib_autosave_filename
, FILE_TYPE_PL
, JOB_TYPE_LIB
);
1910 cmus_add(pl_add_track
, pl_autosave_filename
, FILE_TYPE_PL
, JOB_TYPE_PL
);
1915 warn("Press <enter> to continue.");
1916 fgets(buf
, sizeof(buf
), stdin
);
1922 static void exit_all(void)
1928 remote_server_exit();
1930 cmus_save(lib_for_each
, lib_autosave_filename
);
1931 cmus_save(pl_for_each
, pl_autosave_filename
);
1948 static struct option options
[NR_FLAGS
+ 1] = {
1950 { 0, "plugins", 0 },
1952 { 0, "version", 0 },
1956 static const char *usage
=
1957 "Usage: %s [OPTION]...\n"
1958 "Curses based music player.\n"
1960 " --listen ADDR listen ADDR (unix socket) instead of /tmp/cmus-$USER\n"
1961 " --plugins list available plugins and exit\n"
1962 " --help display this help and exit\n"
1963 " --version " VERSION
"\n"
1965 "Use cmus-remote to control cmus from command line.\n"
1966 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
1968 int main(int argc
, char *argv
[])
1970 int list_plugins
= 0;
1972 program_name
= argv
[0];
1978 idx
= get_option(&argv
, options
, &arg
);
1984 printf(usage
, program_name
);
1987 printf("cmus " VERSION
"\nCopyright 2004-2006 Timo Hirvonen\n");
1993 server_address
= xstrdup(arg
);
1998 setlocale(LC_CTYPE
, "");
2000 charset
= nl_langinfo(CODESET
);
2002 charset
= "ISO-8859-1";
2004 if (strcmp(charset
, "UTF-8") == 0) {
2010 if (server_address
== NULL
)
2011 server_address
= xstrjoin(home_dir
, "/.cmus/socket");
2013 d_print("charset = '%s'\n", charset
);
2015 player_load_plugins();
2017 player_dump_plugins();