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"
37 #include "format_print.h"
53 #include <sys/ioctl.h>
62 #if defined(__sun__) || defined(__CYGWIN__)
70 /* defined in <term.h> but without const */
71 char *tgetstr(const char *id
, char **area
);
72 char *tgoto(const char *cap
, int col
, int row
);
74 /* globals. documented in ui_curses.h */
77 int ui_initialized
= 0;
78 enum ui_input_mode input_mode
= NORMAL_MODE
;
79 int cur_view
= TREE_VIEW
;
80 struct searchable
*searchable
;
81 char *lib_filename
= NULL
;
82 char *pl_filename
= NULL
;
84 /* ------------------------------------------------------------------------- */
86 static char *lib_autosave_filename
;
87 static char *pl_autosave_filename
;
89 /* shown error message and time stamp
90 * error is cleared if it is older than 3s and key was pressed
92 static char error_buf
[512];
93 static time_t error_time
= 0;
94 /* info messages are displayed in different color */
95 static int msg_is_error
;
96 static int error_count
= 0;
98 static char *server_address
= NULL
;
100 static char *charset
= NULL
;
101 static char print_buffer
[512];
103 /* destination buffer for utf8_encode and utf8_decode */
104 static char conv_buffer
[512];
106 #define print_buffer_size (sizeof(print_buffer) - 1)
107 static int using_utf8
;
109 static const char *t_ts
;
110 static const char *t_fs
;
112 static int tree_win_x
= 0;
113 static int tree_win_y
= 0;
114 static int tree_win_w
= 0;
116 static int track_win_x
= 0;
117 static int track_win_y
= 0;
118 static int track_win_w
= 0;
120 static int show_cursor
;
131 CURSED_WIN_ACTIVE_CUR
,
132 CURSED_WIN_ACTIVE_SEL
,
133 CURSED_WIN_ACTIVE_SEL_CUR
,
148 static unsigned char cursed_to_bg_idx
[NR_CURSED
] = {
151 COLOR_WIN_INACTIVE_SEL_BG
,
152 COLOR_WIN_INACTIVE_CUR_SEL_BG
,
157 COLOR_WIN_CUR_SEL_BG
,
170 static unsigned char cursed_to_fg_idx
[NR_CURSED
] = {
173 COLOR_WIN_INACTIVE_SEL_FG
,
174 COLOR_WIN_INACTIVE_CUR_SEL_FG
,
179 COLOR_WIN_CUR_SEL_FG
,
192 /* index is CURSED_*, value is fucking color pair */
193 static int pairs
[NR_CURSED
];
210 static struct format_option track_fopts
[NR_TFS
+ 1] = {
241 static struct format_option status_fopts
[NR_SFS
+ 1] = {
257 static void utf8_encode(const char *buffer
)
259 static iconv_t cd
= (iconv_t
)-1;
265 if (cd
== (iconv_t
)-1) {
266 d_print("iconv_open(UTF-8, %s)\n", charset
);
267 cd
= iconv_open("UTF-8", charset
);
268 if (cd
== (iconv_t
)-1) {
269 d_print("iconv_open failed: %s\n", strerror(errno
));
276 os
= sizeof(conv_buffer
) - 1;
277 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
280 d_print("iconv failed: %s\n", strerror(errno
));
285 static void utf8_decode(const char *buffer
)
287 static iconv_t cd
= (iconv_t
)-1;
293 if (cd
== (iconv_t
)-1) {
294 d_print("iconv_open(%s, UTF-8)\n", charset
);
295 cd
= iconv_open(charset
, "UTF-8");
296 if (cd
== (iconv_t
)-1) {
297 d_print("iconv_open failed: %s\n", strerror(errno
));
304 os
= sizeof(conv_buffer
) - 1;
305 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
308 d_print("iconv failed: %s\n", strerror(errno
));
313 /* screen updates {{{ */
315 static void dump_print_buffer(int row
, int col
)
318 mvaddstr(row
, col
, print_buffer
);
320 utf8_decode(print_buffer
);
321 mvaddstr(row
, col
, conv_buffer
);
325 /* print @str into @buf
327 * if @str is shorter than @width pad with spaces
328 * if @str is wider than @width truncate and add "..."
330 static int format_str(char *buf
, const char *str
, int width
)
332 int s
= 0, d
= 0, ellipsis_pos
= 0, cut_double_width
= 0;
338 u_get_char(str
, &s
, &u
);
340 memset(buf
+ d
, ' ', width
);
348 if (width
== 4 && w
== 2) {
349 /* can't cut double-width char */
350 ellipsis_pos
= d
+ 1;
351 cut_double_width
= 1;
358 if (cut_double_width
) {
359 /* first half of the double-width char */
367 u_set_char(buf
, &d
, u
);
372 static void sprint(int row
, int col
, const char *str
, int width
)
376 print_buffer
[pos
++] = ' ';
377 pos
+= format_str(print_buffer
+ pos
, str
, width
- 2);
378 print_buffer
[pos
++] = ' ';
379 print_buffer
[pos
] = 0;
380 dump_print_buffer(row
, col
);
383 static void sprint_ascii(int row
, int col
, const char *str
, int len
)
390 print_buffer
[0] = ' ';
392 memcpy(print_buffer
+ 1, str
, len
- 3);
393 print_buffer
[len
- 2] = '.';
394 print_buffer
[len
- 1] = '.';
395 print_buffer
[len
- 0] = '.';
397 memcpy(print_buffer
+ 1, str
, l
);
398 memset(print_buffer
+ 1 + l
, ' ', len
- l
);
400 print_buffer
[len
+ 1] = ' ';
401 print_buffer
[len
+ 2] = 0;
402 mvaddstr(row
, col
, print_buffer
);
405 static void print_tree(struct window
*win
, int row
, struct iter
*iter
)
408 struct artist
*artist
;
412 int current
, selected
, active
, pos
;
414 artist
= iter_to_artist(iter
);
415 album
= iter_to_album(iter
);
419 current
= CUR_ALBUM
== album
;
421 current
= CUR_ARTIST
== artist
;
424 window_get_sel(win
, &sel
);
425 selected
= iters_equal(iter
, &sel
);
426 active
= lib_cur_win
== lib_tree_win
;
427 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
429 if (active
&& selected
) {
435 print_buffer
[pos
++] = ' ';
437 print_buffer
[pos
++] = ' ';
438 print_buffer
[pos
++] = ' ';
442 if (pretty_artist_name
&& !strncasecmp(str
, "the ", 4)) {
443 snprintf(buf
, sizeof(buf
), "%s, The", str
+ 4);
447 pos
+= format_str(print_buffer
+ pos
, str
, tree_win_w
- pos
- 1);
448 print_buffer
[pos
++] = ' ';
449 print_buffer
[pos
++] = 0;
450 dump_print_buffer(tree_win_y
+ row
+ 1, tree_win_x
);
453 static inline void fopt_set_str(struct format_option
*fopt
, const char *str
)
455 BUG_ON(fopt
->type
!= FO_STR
);
464 static inline void fopt_set_int(struct format_option
*fopt
, int value
, int empty
)
466 BUG_ON(fopt
->type
!= FO_INT
);
467 fopt
->fo_int
= value
;
471 static inline void fopt_set_time(struct format_option
*fopt
, int value
, int empty
)
473 BUG_ON(fopt
->type
!= FO_TIME
);
474 fopt
->fo_time
= value
;
478 static void fill_track_fopts_track_info(struct track_info
*info
)
484 filename
= info
->filename
;
486 utf8_encode(info
->filename
);
487 filename
= conv_buffer
;
489 disc
= comments_get_int(info
->comments
, "discnumber");
490 num
= comments_get_int(info
->comments
, "tracknumber");
492 fopt_set_str(&track_fopts
[TF_ARTIST
], keyvals_get_val(info
->comments
, "artist"));
493 fopt_set_str(&track_fopts
[TF_ALBUM
], keyvals_get_val(info
->comments
, "album"));
494 fopt_set_int(&track_fopts
[TF_DISC
], disc
, disc
== -1);
495 fopt_set_int(&track_fopts
[TF_TRACK
], num
, num
== -1);
496 fopt_set_str(&track_fopts
[TF_TITLE
], keyvals_get_val(info
->comments
, "title"));
497 fopt_set_str(&track_fopts
[TF_YEAR
], keyvals_get_val(info
->comments
, "date"));
498 fopt_set_str(&track_fopts
[TF_GENRE
], keyvals_get_val(info
->comments
, "genre"));
499 fopt_set_str(&track_fopts
[TF_COMMENT
], keyvals_get_val(info
->comments
, "comment"));
500 fopt_set_time(&track_fopts
[TF_DURATION
], info
->duration
, info
->duration
== -1);
501 fopt_set_str(&track_fopts
[TF_PATHFILE
], filename
);
502 if (is_url(info
->filename
)) {
503 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
507 f
= strrchr(filename
, '/');
509 fopt_set_str(&track_fopts
[TF_FILE
], f
+ 1);
511 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
516 static void print_track(struct window
*win
, int row
, struct iter
*iter
)
518 struct tree_track
*track
;
520 int current
, selected
, active
;
522 track
= iter_to_tree_track(iter
);
523 current
= lib_cur_track
== track
;
524 window_get_sel(win
, &sel
);
525 selected
= iters_equal(iter
, &sel
);
526 active
= lib_cur_win
== lib_track_win
;
527 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
529 if (active
&& selected
) {
530 cursor_x
= track_win_x
;
534 fill_track_fopts_track_info(tree_track_info(track
));
536 if (track_info_has_tag(tree_track_info(track
))) {
537 format_print(print_buffer
, track_win_w
, track_win_format
, track_fopts
);
539 format_print(print_buffer
, track_win_w
, track_win_alt_format
, track_fopts
);
541 dump_print_buffer(track_win_y
+ row
+ 1, track_win_x
);
544 /* used by print_editable only */
545 static struct simple_track
*current_track
;
547 static void print_editable(struct window
*win
, int row
, struct iter
*iter
)
549 struct simple_track
*track
;
551 int current
, selected
, active
;
553 track
= iter_to_simple_track(iter
);
554 current
= current_track
== track
;
555 window_get_sel(win
, &sel
);
556 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
]);
608 /* file name encoding == terminal encoding. no need to convert */
610 sprint(row
+ 1, 0, e
->name
, COLS
);
612 sprint_ascii(row
+ 1, 0, e
->name
, COLS
);
616 static void print_filter(struct window
*win
, int row
, struct iter
*iter
)
619 struct filter_entry
*e
= iter_to_filter_entry(iter
);
625 /* is the filter currently active? */
626 int current
= !!e
->act_stat
;
627 const char stat_chars
[3] = " *!";
628 int ch1
, ch2
, ch3
, pos
;
630 window_get_sel(win
, &sel
);
631 selected
= iters_equal(iter
, &sel
);
632 bkgdset(pairs
[(active
<< 2) | (selected
<< 1) | current
]);
641 if (e
->sel_stat
!= e
->act_stat
) {
645 ch2
= stat_chars
[e
->sel_stat
];
646 snprintf(buf
, sizeof(buf
), "%c%c%c%-15s %s", ch1
, ch2
, ch3
, e
->name
, e
->filter
);
647 pos
= format_str(print_buffer
, buf
, COLS
- 1);
648 print_buffer
[pos
++] = ' ';
649 print_buffer
[pos
] = 0;
650 dump_print_buffer(row
+ 1, 0);
653 static void print_help(struct window
*win
, int row
, struct iter
*iter
)
660 const struct help_entry
*e
= iter_to_help_entry(iter
);
661 const struct cmus_opt
*opt
;
663 window_get_sel(win
, &sel
);
664 selected
= iters_equal(iter
, &sel
);
665 bkgdset(pairs
[(active
<< 2) | (selected
<< 1)]);
674 snprintf(buf
, sizeof(buf
), " %s", e
->text
);
677 snprintf(buf
, sizeof(buf
), " %-8s %-14s %s",
678 key_context_names
[e
->binding
->ctx
],
679 e
->binding
->key
->name
,
683 snprintf(buf
, sizeof(buf
), " %s", e
->command
->name
);
687 snprintf(buf
, sizeof(buf
), " %-29s ", opt
->name
);
688 opt
->get(opt
->id
, buf
+ strlen(buf
));
691 pos
= format_str(print_buffer
, buf
, COLS
- 1);
692 print_buffer
[pos
++] = ' ';
693 print_buffer
[pos
] = 0;
694 dump_print_buffer(row
+ 1, 0);
697 static void update_window(struct window
*win
, int x
, int y
, int w
, const char *title
,
698 void (*print
)(struct window
*, int, struct iter
*))
706 bkgdset(pairs
[CURSED_WIN_TITLE
]);
707 c
= snprintf(print_buffer
, w
+ 1, " %s", title
);
710 memset(print_buffer
+ c
, ' ', w
- c
+ 1);
712 dump_print_buffer(y
, x
);
713 nr_rows
= window_get_nr_rows(win
);
715 if (window_get_top(win
, &iter
)) {
716 while (i
< nr_rows
) {
717 print(win
, i
, &iter
);
719 if (!window_get_next(win
, &iter
))
725 memset(print_buffer
, ' ', w
);
727 while (i
< nr_rows
) {
728 dump_print_buffer(y
+ i
+ 1, x
);
733 static void update_tree_window(void)
735 update_window(lib_tree_win
, tree_win_x
, tree_win_y
,
736 tree_win_w
, "Artist / Album", print_tree
);
739 static void update_track_window(void)
743 /* it doesn't matter what format options we use because the format
744 * string does not contain any format charaters */
745 format_print(title
, track_win_w
- 2, "Track%=Library", track_fopts
);
746 update_window(lib_track_win
, track_win_x
, track_win_y
,
747 track_win_w
, title
, print_track
);
750 static const char *pretty(const char *path
)
752 static int home_len
= -1;
753 static char buf
[256];
756 home_len
= strlen(home_dir
);
758 if (strncmp(path
, home_dir
, home_len
) || path
[home_len
] != '/')
762 strcpy(buf
+ 1, path
+ home_len
);
766 static const char * const sorted_names
[2] = { "", "sorted by " };
768 static void update_editable_window(struct editable
*e
, const char *title
, const char *filename
)
777 utf8_encode(filename
);
778 filename
= conv_buffer
;
780 snprintf(buf
, sizeof(buf
), "%s %s - %d tracks", title
,
781 pretty(filename
), e
->nr_tracks
);
783 snprintf(buf
, sizeof(buf
), "%s - %d tracks", title
, e
->nr_tracks
);
788 snprintf(buf
+ pos
, sizeof(buf
) - pos
, " (%d marked)", e
->nr_marked
);
791 snprintf(buf
+ pos
, sizeof(buf
) - pos
, " %s%s",
792 sorted_names
[e
->sort_str
[0] != 0], e
->sort_str
);
794 update_window(e
->win
, 0, 0, COLS
, buf
, &print_editable
);
797 static void update_sorted_window(void)
799 current_track
= (struct simple_track
*)lib_cur_track
;
800 update_editable_window(&lib_editable
, "Library", lib_filename
);
803 static void update_pl_window(void)
805 current_track
= pl_cur_track
;
806 update_editable_window(&pl_editable
, "Playlist", pl_filename
);
809 static void update_play_queue_window(void)
811 current_track
= NULL
;
812 update_editable_window(&pq_editable
, "Play Queue", NULL
);
815 static void update_browser_window(void)
822 dirname
= browser_dir
;
824 utf8_encode(browser_dir
);
825 dirname
= conv_buffer
;
827 snprintf(title
, sizeof(title
), "Browser - %s", dirname
);
828 update_window(browser_win
, 0, 0, COLS
, title
, print_browser
);
831 static void update_filters_window(void)
833 update_window(filters_win
, 0, 0, COLS
, "Library Filters", print_filter
);
836 static void update_help_window(void)
838 update_window(help_win
, 0, 0, COLS
, "Settings", print_help
);
841 static void draw_separator(void)
845 bkgdset(pairs
[CURSED_WIN_TITLE
]);
846 mvaddch(0, tree_win_w
, ' ');
847 bkgdset(pairs
[CURSED_SEPARATOR
]);
848 for (row
= 1; row
< LINES
- 3; row
++)
849 mvaddch(row
, tree_win_w
, ACS_VLINE
);
852 static void do_update_view(int full
)
860 if (full
|| lib_tree_win
->changed
)
861 update_tree_window();
862 if (full
|| lib_track_win
->changed
)
863 update_track_window();
869 update_sorted_window();
879 update_play_queue_window();
883 update_browser_window();
886 update_filters_window();
889 update_help_window();
894 static void do_update_statusline(void)
896 static const char *status_strs
[] = { ".", ">", "|" };
897 static const char *cont_strs
[] = { " ", "C" };
898 static const char *repeat_strs
[] = { " ", "R" };
899 static const char *shuffle_strs
[] = { " ", "S" };
900 int buffer_fill
, vol
, vol_left
, vol_right
;
906 fopt_set_time(&status_fopts
[SF_TOTAL
], play_library
? lib_editable
.total_time
:
907 pl_editable
.total_time
, 0);
910 fopt_set_str(&status_fopts
[SF_REPEAT
], repeat_strs
[repeat
]);
911 fopt_set_str(&status_fopts
[SF_SHUFFLE
], shuffle_strs
[shuffle
]);
912 fopt_set_str(&status_fopts
[SF_PLAYLISTMODE
], aaa_mode_names
[aaa_mode
]);
917 duration
= player_info
.ti
->duration
;
920 vol_left
= soft_vol_l
;
921 vol_right
= soft_vol_r
;
922 vol
= (vol_left
+ vol_right
+ 1) / 2;
923 } else if (!volume_max
) {
924 vol_left
= vol_right
= vol
= -1;
926 vol_left
= scale_to_percentage(volume_l
, volume_max
);
927 vol_right
= scale_to_percentage(volume_r
, volume_max
);
928 vol
= (vol_left
+ vol_right
+ 1) / 2;
930 buffer_fill
= scale_to_percentage(player_info
.buffer_fill
, player_info
.buffer_size
);
932 fopt_set_str(&status_fopts
[SF_STATUS
], status_strs
[player_info
.status
]);
934 if (show_remaining_time
&& duration
!= -1) {
935 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
- duration
, 0);
937 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
, 0);
940 fopt_set_time(&status_fopts
[SF_DURATION
], duration
, 0);
941 fopt_set_int(&status_fopts
[SF_VOLUME
], vol
, 0);
942 fopt_set_int(&status_fopts
[SF_LVOLUME
], vol_left
, 0);
943 fopt_set_int(&status_fopts
[SF_RVOLUME
], vol_right
, 0);
944 fopt_set_int(&status_fopts
[SF_BUFFER
], buffer_fill
, 0);
945 fopt_set_str(&status_fopts
[SF_CONTINUE
], cont_strs
[player_cont
]);
947 strcpy(format
, " %s %p ");
949 strcat(format
, "/ %d ");
950 strcat(format
, "- %t ");
952 if (vol_left
!= vol_right
) {
953 strcat(format
, "vol: %l,%r ");
955 strcat(format
, "vol: %v ");
958 if (player_info
.ti
&& is_url(player_info
.ti
->filename
))
959 strcat(format
, "buf: %b ");
960 strcat(format
, "%=");
961 if (player_repeat_current
) {
962 strcat(format
, "repeat current");
963 } else if (play_library
) {
964 /* artist/album modes work only in lib */
966 /* shuffle overrides sorted mode */
967 strcat(format
, "%L from library");
968 } else if (play_sorted
) {
969 strcat(format
, "%L from sorted library");
971 strcat(format
, "%L from library");
974 strcat(format
, "playlist");
976 strcat(format
, " | %1C%1R%1S ");
977 format_print(print_buffer
, COLS
, format
, status_fopts
);
979 msg
= player_info
.error_msg
;
980 player_info
.error_msg
= NULL
;
982 player_info_unlock();
984 bkgdset(pairs
[CURSED_STATUSLINE
]);
985 dump_print_buffer(LINES
- 2, 0);
988 error_msg("%s", msg
);
993 static void dump_buffer(const char *buffer
)
1003 static void do_update_commandline(void)
1012 bkgdset(pairs
[CURSED_ERROR
]);
1014 bkgdset(pairs
[CURSED_INFO
]);
1020 bkgdset(pairs
[CURSED_COMMANDLINE
]);
1021 if (input_mode
== NORMAL_MODE
) {
1028 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1029 * characters are invalid UTF-8 so it really is in locale's
1032 * This code should be safe because cmdline.bpos ==
1033 * cmdline.cpos as every non-ASCII character is counted as one
1034 * invalid UTF-8 byte.
1036 * NOTE: This has nothing to do with widths of printed
1037 * characters. I.e. even if there were control characters
1038 * (displayed as <xx>) there would be no problem because bpos
1039 * still equals to cpos, I think.
1041 utf8_encode(cmdline
.line
);
1045 /* COMMAND_MODE or SEARCH_MODE */
1046 w
= u_str_width(str
);
1048 if (input_mode
== SEARCH_MODE
)
1049 ch
= search_direction
== SEARCH_FORWARD
? '/' : '?';
1051 if (w
<= COLS
- 2) {
1053 idx
= u_copy_chars(print_buffer
, str
, &w
);
1054 print_buffer
[idx
] = 0;
1055 dump_buffer(print_buffer
);
1058 /* keep cursor as far right as possible */
1059 int skip
, width
, cw
;
1061 /* cursor pos (width, not chars. doesn't count the ':') */
1062 cw
= u_str_nwidth(str
, cmdline
.cpos
);
1064 skip
= cw
+ 2 - COLS
;
1069 /* skip rest (if any) */
1070 idx
= u_skip_chars(str
, &skip
);
1073 idx
= u_copy_chars(print_buffer
, str
+ idx
, &width
);
1074 while (width
< COLS
) {
1075 /* cursor is at end of the buffer
1076 * print 1, 2 or 3 spaces
1080 * If the last _skipped_ character was double-width we may need
1081 * to print 2 spaces.
1083 * If the last _skipped_ character was invalid UTF-8 we may need
1084 * to print 3 spaces.
1086 print_buffer
[idx
++] = ' ';
1089 print_buffer
[idx
] = 0;
1090 dump_buffer(print_buffer
);
1092 /* print ':' + COLS - 1 chars */
1095 idx
= u_copy_chars(print_buffer
, str
, &width
);
1096 print_buffer
[idx
] = 0;
1097 dump_buffer(print_buffer
);
1102 /* lock player_info! */
1103 static const char *get_stream_title(void)
1105 static char stream_title
[255 * 16 + 1];
1108 ptr
= strstr(player_info
.metadata
, "StreamTitle='");
1114 if (*ptr
== '\'' && *(ptr
+ 1) == ';') {
1115 memcpy(stream_title
, title
, ptr
- title
);
1116 stream_title
[ptr
- title
] = 0;
1117 return stream_title
;
1124 static void set_title(const char *title
)
1126 if (!set_term_title
)
1130 printf("%s%s%s", tgoto(t_ts
, 0, 0), title
, t_fs
);
1135 static void do_update_titleline(void)
1137 bkgdset(pairs
[CURSED_TITLELINE
]);
1139 if (player_info
.ti
) {
1140 int i
, use_alt_format
= 0;
1143 fill_track_fopts_track_info(player_info
.ti
);
1144 if (is_url(player_info
.ti
->filename
)) {
1145 const char *title
= get_stream_title();
1149 fopt_set_str(&track_fopts
[TF_TITLE
], title
);
1151 use_alt_format
= !track_info_has_tag(player_info
.ti
);
1154 if (use_alt_format
) {
1155 format_print(print_buffer
, COLS
, current_alt_format
, track_fopts
);
1157 format_print(print_buffer
, COLS
, current_format
, track_fopts
);
1159 dump_print_buffer(LINES
- 3, 0);
1161 /* set window title */
1162 if (use_alt_format
) {
1163 format_print(print_buffer
, sizeof(print_buffer
) - 1,
1164 window_title_alt_format
, track_fopts
);
1166 format_print(print_buffer
, sizeof(print_buffer
) - 1,
1167 window_title_format
, track_fopts
);
1170 /* remove whitespace */
1171 i
= strlen(print_buffer
) - 1;
1172 while (i
> 0 && print_buffer
[i
] == ' ')
1174 print_buffer
[i
+ 1] = 0;
1177 wtitle
= print_buffer
;
1179 utf8_decode(print_buffer
);
1180 wtitle
= conv_buffer
;
1188 set_title("cmus " VERSION
);
1190 player_info_unlock();
1193 static int cmdline_cursor_column(void)
1200 /* see do_update_commandline */
1201 utf8_encode(cmdline
.line
);
1205 /* width of the text in the buffer before cursor */
1206 cw
= u_str_nwidth(str
, cmdline
.cpos
);
1208 if (1 + cw
< COLS
) {
1209 /* whole line is visible */
1213 /* beginning of cmdline is not visible */
1215 /* check if the first visible char in cmdline would be halved
1216 * double-width character (or invalid byte <xx>) which is not possible.
1217 * we need to skip the whole character and move cursor to COLS - 2
1219 skip
= cw
+ 2 - COLS
;
1226 u_skip_chars(str
, &s
);
1228 /* the last skipped char was double-width or <xx> */
1229 return COLS
- 1 - (s
- skip
);
1234 static void post_update(void)
1236 /* refresh makes cursor visible at least for urxvt */
1237 if (input_mode
== COMMAND_MODE
|| input_mode
== SEARCH_MODE
) {
1238 move(LINES
- 1, cmdline_cursor_column());
1242 if (cursor_x
>= 0) {
1243 move(cursor_y
, cursor_x
);
1249 /* visible cursor is useful for screen readers */
1258 void update_titleline(void)
1261 do_update_titleline();
1265 void update_full(void)
1267 if (!ui_initialized
)
1273 do_update_titleline();
1274 do_update_statusline();
1275 do_update_commandline();
1280 static void update_commandline(void)
1283 do_update_commandline();
1287 void update_statusline(void)
1289 if (!ui_initialized
)
1293 do_update_statusline();
1297 void info_msg(const char *format
, ...)
1301 va_start(ap
, format
);
1302 vsnprintf(error_buf
, sizeof(error_buf
), format
, ap
);
1307 update_commandline();
1310 void error_msg(const char *format
, ...)
1314 strcpy(error_buf
, "Error: ");
1315 va_start(ap
, format
);
1316 vsnprintf(error_buf
+ 7, sizeof(error_buf
) - 7, format
, ap
);
1319 d_print("%s\n", error_buf
);
1324 if (ui_initialized
) {
1325 error_time
= time(NULL
);
1326 update_commandline();
1328 warn("%s\n", error_buf
);
1333 int yes_no_query(const char *format
, ...)
1339 va_start(ap
, format
);
1340 vsnprintf(buffer
, sizeof(buffer
), format
, ap
);
1344 bkgdset(pairs
[CURSED_INFO
]);
1346 /* no need to convert buffer.
1347 * it is always encoded in the right charset (assuming filenames are
1348 * encoded in same charset as LC_CTYPE).
1358 if (ch
== ERR
|| ch
== 0)
1364 update_commandline();
1368 void search_not_found(void)
1370 const char *what
= "Track";
1372 if (search_restricted
) {
1375 what
= "Artist/album";
1383 what
= "File/Directory";
1389 what
= "Binding/command/option";
1401 what
= "File/Directory";
1407 what
= "Binding/command/option";
1411 info_msg("%s not found: %s", what
, search_str
? : "");
1414 void set_view(int view
)
1416 if (view
== cur_view
)
1422 searchable
= tree_searchable
;
1425 searchable
= lib_editable
.searchable
;
1428 searchable
= pl_editable
.searchable
;
1431 searchable
= pq_editable
.searchable
;
1434 searchable
= browser_searchable
;
1437 searchable
= filters_searchable
;
1440 searchable
= help_searchable
;
1441 update_help_window();
1450 void enter_command_mode(void)
1454 input_mode
= COMMAND_MODE
;
1455 update_commandline();
1458 void enter_search_mode(void)
1462 input_mode
= SEARCH_MODE
;
1463 search_direction
= SEARCH_FORWARD
;
1464 update_commandline();
1467 void enter_search_backward_mode(void)
1471 input_mode
= SEARCH_MODE
;
1472 search_direction
= SEARCH_BACKWARD
;
1473 update_commandline();
1476 void update_colors(void)
1480 if (!ui_initialized
)
1483 for (i
= 0; i
< NR_CURSED
; i
++) {
1484 int bg
= colors
[cursed_to_bg_idx
[i
]];
1485 int fg
= colors
[cursed_to_fg_idx
[i
]];
1488 if (fg
>= 8 && fg
<= 15) {
1489 /* fg colors 8..15 are special (0..7 + bold) */
1490 init_pair(pair
, fg
& 7, bg
);
1491 pairs
[i
] = COLOR_PAIR(pair
) | (fg
& BRIGHT
? A_BOLD
: 0);
1493 init_pair(pair
, fg
, bg
);
1494 pairs
[i
] = COLOR_PAIR(pair
);
1499 static void clear_error(void)
1501 time_t t
= time(NULL
);
1503 /* prevent accidental clearing of error messages */
1504 if (t
- error_time
< 2)
1510 update_commandline();
1514 /* screen updates }}} */
1516 static void spawn_status_program(void)
1518 static const char *status_strs
[] = { "stopped", "playing", "paused" };
1519 const char *stream_title
= NULL
;
1523 if (status_display_program
== NULL
|| status_display_program
[0] == 0)
1527 status
= player_info
.status
;
1529 stream_title
= get_stream_title();
1532 argv
[i
++] = xstrdup(status_display_program
);
1534 argv
[i
++] = xstrdup("status");
1535 argv
[i
++] = xstrdup(status_strs
[status
]);
1536 if (player_info
.ti
) {
1537 static const char *keys
[] = {
1538 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1542 if (is_url(player_info
.ti
->filename
)) {
1543 argv
[i
++] = xstrdup("url");
1544 argv
[i
++] = xstrdup(player_info
.ti
->filename
);
1546 argv
[i
++] = xstrdup("title");
1547 argv
[i
++] = xstrdup(stream_title
);
1552 argv
[i
++] = xstrdup("file");
1553 argv
[i
++] = xstrdup(player_info
.ti
->filename
);
1554 for (j
= 0; keys
[j
]; j
++) {
1555 const char *key
= keys
[j
];
1558 val
= keyvals_get_val(player_info
.ti
->comments
, key
);
1560 argv
[i
++] = xstrdup(key
);
1561 argv
[i
++] = xstrdup(val
);
1564 snprintf(buf
, sizeof(buf
), "%d", player_info
.ti
->duration
);
1565 argv
[i
++] = xstrdup("duration");
1566 argv
[i
++] = xstrdup(buf
);
1570 player_info_unlock();
1572 if (spawn(argv
, &status
) == -1)
1573 error_msg("couldn't run `%s': %s", status_display_program
, strerror(errno
));
1574 for (i
= 0; argv
[i
]; i
++)
1578 static int ctrl_c_pressed
= 0;
1580 static void sig_int(int sig
)
1585 static void sig_hup(int sig
)
1590 static int needs_to_resize
= 1;
1592 static void sig_winch(int sig
)
1594 needs_to_resize
= 1;
1597 static int get_window_size(int *lines
, int *columns
)
1601 if (ioctl(0, TIOCGWINSZ
, &ws
) == -1)
1603 *columns
= ws
.ws_col
;
1608 static void resize_tree_view(int w
, int h
)
1611 track_win_w
= w
- tree_win_w
- 1;
1614 if (track_win_w
< 8)
1618 track_win_x
= tree_win_w
+ 1;
1622 window_set_nr_rows(lib_tree_win
, h
);
1623 window_set_nr_rows(lib_track_win
, h
);
1626 static void update(void)
1628 int needs_view_update
= 0;
1629 int needs_title_update
= 0;
1630 int needs_status_update
= 0;
1631 int needs_command_update
= 0;
1632 int needs_spawn
= 0;
1634 if (needs_to_resize
) {
1638 if (get_window_size(&lines
, &columns
) == 0) {
1639 needs_to_resize
= 0;
1640 resizeterm(lines
, columns
);
1648 resize_tree_view(w
, h
);
1649 window_set_nr_rows(lib_editable
.win
, h
- 1);
1650 window_set_nr_rows(pl_editable
.win
, h
- 1);
1651 window_set_nr_rows(pq_editable
.win
, h
- 1);
1652 window_set_nr_rows(filters_win
, h
- 1);
1653 window_set_nr_rows(help_win
, h
- 1);
1654 window_set_nr_rows(browser_win
, h
- 1);
1656 needs_title_update
= 1;
1657 needs_status_update
= 1;
1658 needs_command_update
= 1;
1665 needs_spawn
= player_info
.status_changed
|| player_info
.file_changed
||
1666 player_info
.metadata_changed
;
1668 if (player_info
.file_changed
) {
1669 player_info
.file_changed
= 0;
1670 needs_title_update
= 1;
1671 needs_status_update
= 1;
1673 if (player_info
.metadata_changed
) {
1674 player_info
.metadata_changed
= 0;
1675 needs_title_update
= 1;
1677 if (player_info
.position_changed
|| player_info
.status_changed
) {
1678 player_info
.position_changed
= 0;
1679 player_info
.status_changed
= 0;
1681 needs_status_update
= 1;
1685 needs_view_update
+= lib_tree_win
->changed
|| lib_track_win
->changed
;
1688 needs_view_update
+= lib_editable
.win
->changed
;
1691 needs_view_update
+= pl_editable
.win
->changed
;
1694 needs_view_update
+= pq_editable
.win
->changed
;
1697 needs_view_update
+= browser_win
->changed
;
1700 needs_view_update
+= filters_win
->changed
;
1703 needs_view_update
+= help_win
->changed
;
1707 /* total time changed? */
1709 needs_status_update
+= lib_editable
.win
->changed
;
1710 lib_editable
.win
->changed
= 0;
1712 needs_status_update
+= pl_editable
.win
->changed
;
1713 lib_editable
.win
->changed
= 0;
1717 player_info_unlock();
1720 spawn_status_program();
1722 if (needs_view_update
|| needs_title_update
|| needs_status_update
|| needs_command_update
) {
1725 if (needs_view_update
)
1727 if (needs_title_update
)
1728 do_update_titleline();
1729 if (needs_status_update
)
1730 do_update_statusline();
1731 if (needs_command_update
)
1732 do_update_commandline();
1737 static void handle_ch(uchar ch
)
1740 if (input_mode
== NORMAL_MODE
) {
1742 } else if (input_mode
== COMMAND_MODE
) {
1743 command_mode_ch(ch
);
1744 update_commandline();
1745 } else if (input_mode
== SEARCH_MODE
) {
1747 update_commandline();
1751 static void handle_key(int key
)
1754 if (input_mode
== NORMAL_MODE
) {
1755 normal_mode_key(key
);
1756 } else if (input_mode
== COMMAND_MODE
) {
1757 command_mode_key(key
);
1758 update_commandline();
1759 } else if (input_mode
== SEARCH_MODE
) {
1760 search_mode_key(key
);
1761 update_commandline();
1765 static void u_getch(void)
1769 int mask
= (1 << 7);
1773 if (key
== ERR
|| key
== 0)
1781 ch
= (unsigned char)key
;
1782 while (bit
> 0 && ch
& mask
) {
1792 u
= ch
& ((1 << bit
) - 1);
1796 if (key
== ERR
|| key
== 0)
1799 ch
= (unsigned char)key
;
1800 u
= (u
<< 6) | (ch
& 63);
1807 static void main_loop(void)
1811 fd_high
= server_socket
;
1812 while (cmus_running
) {
1817 int fds
[NR_MIXER_FDS
];
1818 struct list_head
*item
;
1819 struct client
*client
;
1823 /* Timeout must be so small that screen updates seem instant.
1824 * Only affects changes done in other threads (worker, player).
1826 * Too small timeout makes window updates too fast (wastes CPU).
1828 * Too large timeout makes status line (position) updates too slow.
1829 * The timeout is accuracy of player position.
1835 if (player_info
.status
== PLAYER_STATUS_PLAYING
) {
1836 // player position updates need to be fast
1839 player_info_unlock();
1841 if (!tv
.tv_usec
&& worker_has_job(JOB_TYPE_ANY
)) {
1842 // playlist is loading. screen needs to be updated
1848 FD_SET(server_socket
, &set
);
1849 list_for_each_entry(client
, &client_head
, node
) {
1850 FD_SET(client
->fd
, &set
);
1851 if (client
->fd
> fd_high
)
1852 fd_high
= client
->fd
;
1855 nr_fds
= mixer_get_fds(fds
);
1856 if (nr_fds
== -OP_ERROR_NOT_SUPPORTED
) {
1857 // mixer has no pollable file descriptors
1862 for (i
= 0; i
< nr_fds
; i
++) {
1863 BUG_ON(fds
[i
] <= 0);
1864 FD_SET(fds
[i
], &set
);
1865 if (fds
[i
] > fd_high
)
1871 rc
= select(fd_high
+ 1, &set
, NULL
, NULL
, &tv
);
1873 rc
= select(fd_high
+ 1, &set
, NULL
, NULL
, NULL
);
1879 mixer_read_volume();
1880 if (ol
!= volume_l
|| or != volume_r
)
1881 update_statusline();
1885 if (ctrl_c_pressed
) {
1893 for (i
= 0; i
< nr_fds
; i
++) {
1894 if (FD_ISSET(fds
[i
], &set
)) {
1895 d_print("vol changed\n");
1896 mixer_read_volume();
1897 update_statusline();
1900 if (FD_ISSET(server_socket
, &set
))
1903 // server_serve() can remove client from the list
1904 item
= client_head
.next
;
1905 while (item
!= &client_head
) {
1906 struct list_head
*next
= item
->next
;
1907 client
= container_of(item
, struct client
, node
);
1908 if (FD_ISSET(client
->fd
, &set
))
1909 server_serve(client
);
1913 if (FD_ISSET(0, &set
)) {
1919 if (key
!= ERR
&& key
!= 0) {
1926 ch
|= U_INVALID_MASK
;
1935 static int get_next(struct track_info
**ti
)
1937 struct track_info
*info
;
1940 info
= play_queue_remove();
1943 info
= lib_set_next();
1945 info
= pl_set_next();
1957 static const struct player_callbacks player_callbacks
= {
1958 .get_next
= get_next
1961 static void init_curses(void)
1963 struct sigaction act
;
1966 sigemptyset(&act
.sa_mask
);
1968 act
.sa_handler
= sig_int
;
1969 sigaction(SIGINT
, &act
, NULL
);
1971 sigemptyset(&act
.sa_mask
);
1973 act
.sa_handler
= sig_hup
;
1974 sigaction(SIGHUP
, &act
, NULL
);
1976 sigemptyset(&act
.sa_mask
);
1978 act
.sa_handler
= SIG_IGN
;
1979 sigaction(SIGPIPE
, &act
, NULL
);
1981 sigemptyset(&act
.sa_mask
);
1983 act
.sa_handler
= sig_winch
;
1984 sigaction(SIGWINCH
, &act
, NULL
);
1988 /* turn off kb buffering */
1991 keypad(stdscr
, TRUE
);
1993 /* wait max 5 * 0.1 s if there are no keys available
1994 * doesn't really matter because we use select()
2001 use_default_colors();
2003 d_print("Number of supported colors: %d\n", COLORS
);
2006 /* this was disabled while initializing because it needs to be
2007 * called only once after all colors have been set
2012 t_ts
= tgetstr("ts", &ptr
);
2013 t_fs
= tgetstr("fs", &ptr
);
2014 d_print("ts: %d fs: %d\n", !!t_ts
, !!t_fs
);
2019 term
= getenv("TERM");
2020 if (!t_ts
&& term
) {
2025 * terminal (xfce): xterm
2026 * urxvt: rxvt-unicode
2027 * xterm: xterm, xterm-{,16,88,256}color
2029 if (!strcmp(term
, "screen")) {
2032 } else if (!strncmp(term
, "xterm", 5) ||
2033 !strncmp(term
, "rxvt", 4) ||
2034 !strcmp(term
, "Eterm")) {
2035 /* \033]1; change icon
2036 * \033]2; change title
2037 * \033]0; change both
2045 static void init_all(void)
2047 server_init(server_address
);
2049 /* does not select output plugin */
2050 player_init(&player_callbacks
);
2052 /* plugins have been loaded so we know what plugin options are available */
2056 searchable
= tree_searchable
;
2066 /* almost everything must be initialized now */
2069 /* finally we can set the output plugin */
2070 player_set_op(output_plugin
);
2074 lib_autosave_filename
= xstrjoin(cmus_config_dir
, "/lib.pl");
2075 pl_autosave_filename
= xstrjoin(cmus_config_dir
, "/playlist.pl");
2076 pl_filename
= xstrdup(pl_autosave_filename
);
2077 lib_filename
= xstrdup(lib_autosave_filename
);
2079 cmus_add(lib_add_track
, lib_autosave_filename
, FILE_TYPE_PL
, JOB_TYPE_LIB
);
2080 cmus_add(pl_add_track
, pl_autosave_filename
, FILE_TYPE_PL
, JOB_TYPE_PL
);
2085 warn("Press <enter> to continue.");
2086 fgets(buf
, sizeof(buf
), stdin
);
2088 help_add_all_unbound();
2093 static void exit_all(void)
2101 cmus_save(lib_for_each
, lib_autosave_filename
);
2102 cmus_save(pl_for_each
, pl_autosave_filename
);
2122 static struct option options
[NR_FLAGS
+ 1] = {
2124 { 0, "plugins", 0 },
2125 { 0, "show-cursor", 0 },
2127 { 0, "version", 0 },
2131 static const char *usage
=
2132 "Usage: %s [OPTION]...\n"
2133 "Curses based music player.\n"
2135 " --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
2136 " ADDR is either a UNIX socket or host[:port]\n"
2137 " WARNING: using TCP/IP is insecure!\n"
2138 " --plugins list available plugins and exit\n"
2139 " --show-cursor always visible cursor\n"
2140 " --help display this help and exit\n"
2141 " --version " VERSION
"\n"
2143 "Use cmus-remote to control cmus from command line.\n"
2144 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2146 int main(int argc
, char *argv
[])
2148 int list_plugins
= 0;
2150 program_name
= argv
[0];
2156 idx
= get_option(&argv
, options
, &arg
);
2162 printf(usage
, program_name
);
2165 printf("cmus " VERSION
"\nCopyright 2004-2006 Timo Hirvonen\n");
2171 server_address
= xstrdup(arg
);
2173 case FLAG_SHOW_CURSOR
:
2179 setlocale(LC_CTYPE
, "");
2181 charset
= nl_langinfo(CODESET
);
2183 charset
= "ISO-8859-1";
2185 if (strcmp(charset
, "UTF-8") == 0) {
2191 if (server_address
== NULL
)
2192 server_address
= xstrjoin(cmus_config_dir
, "/socket");
2194 d_print("charset = '%s'\n", charset
);