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>
35 #include <format_print.h>
41 #include <get_option.h>
47 #if defined(CONFIG_IRMAN)
49 #include <irman_config.h>
58 #include <sys/ioctl.h>
67 /* globals. documented in ui_curses.h */
69 char *program_name
= NULL
;
71 enum ui_curses_input_mode ui_curses_input_mode
= NORMAL_MODE
;
72 int ui_curses_view
= TREE_VIEW
;
73 struct searchable
*searchable
;
75 char *playlist_autosave_filename
;
76 char *playlist_filename
= NULL
;
77 char *track_win_format
= NULL
;
78 char *track_win_alt_format
= NULL
;
79 char *list_win_format
= NULL
;
80 char *list_win_alt_format
= NULL
;
81 char *current_format
= NULL
;
82 char *current_alt_format
= NULL
;
83 char *window_title_format
= NULL
;
84 char *window_title_alt_format
= NULL
;
85 char *status_display_program
= NULL
;
87 char *sort_string
= NULL
;
89 #define BRIGHT (1 << 3)
91 int bg_colors
[NR_COLORS
] = {
112 int fg_colors
[NR_COLORS
] = {
114 COLOR_YELLOW
| BRIGHT
,
116 COLOR_YELLOW
| BRIGHT
,
118 COLOR_YELLOW
| BRIGHT
,
119 COLOR_WHITE
| BRIGHT
,
120 COLOR_YELLOW
| BRIGHT
,
123 COLOR_WHITE
| BRIGHT
,
126 COLOR_WHITE
| BRIGHT
,
130 COLOR_YELLOW
| BRIGHT
133 /* prefixes actually. "_bg" or "_fg" added at end */
134 const char * const color_names
[NR_COLORS
] = {
142 "row_active_sel_cur",
154 /* ------------------------------------------------------------------------- */
156 /* currently playing file */
157 static struct track_info
*cur_track_info
;
159 static int show_remaining_time
= 0;
160 static int update_window_title
= 0;
162 static int running
= 1;
164 /* shown error message and time stamp
165 * error is cleared if it is older than 3s and key was pressed
167 static char error_msg
[512];
168 static time_t error_time
= 0;
170 static char *server_address
= NULL
;
171 static int remote_socket
= -1;
173 static char *config_filename
;
174 static char *charset
= NULL
;
175 static char print_buffer
[512];
177 /* destination buffer for utf8_encode and utf8_decode */
178 static char conv_buffer
[512];
180 #define print_buffer_size (sizeof(print_buffer) - 1)
181 static int using_utf8
;
183 static int tree_win_x
= 0;
184 static int tree_win_y
= 0;
185 static int tree_win_w
= 0;
187 static int track_win_x
= 0;
188 static int track_win_y
= 0;
189 static int track_win_w
= 0;
191 /* colors in curses format */
192 static int cursed_colors
[NR_COLORS
];
198 static void utf8_encode(const char *buffer
)
200 static iconv_t cd
= (iconv_t
)-1;
206 if (cd
== (iconv_t
)-1) {
207 d_print("iconv_open(UTF-8, %s)\n", charset
);
208 cd
= iconv_open("UTF-8", charset
);
209 if (cd
== (iconv_t
)-1) {
210 d_print("iconv_open failed: %s\n", strerror(errno
));
217 os
= sizeof(conv_buffer
) - 1;
218 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
221 d_print("iconv failed: %s\n", strerror(errno
));
226 static void utf8_decode(const char *buffer
)
228 static iconv_t cd
= (iconv_t
)-1;
234 if (cd
== (iconv_t
)-1) {
235 d_print("iconv_open(%s, UTF-8)\n", charset
);
236 cd
= iconv_open(charset
, "UTF-8");
237 if (cd
== (iconv_t
)-1) {
238 d_print("iconv_open failed: %s\n", strerror(errno
));
245 os
= sizeof(conv_buffer
) - 1;
246 rc
= iconv(cd
, (void *)&i
, &is
, &o
, &os
);
249 d_print("iconv failed: %s\n", strerror(errno
));
254 /* screen updates {{{ */
256 static void dump_print_buffer(int row
, int col
)
259 mvaddstr(row
, col
, print_buffer
);
261 utf8_decode(print_buffer
);
262 mvaddstr(row
, col
, conv_buffer
);
266 static void sprint(int row
, int col
, const char *str
, int width
, int indent
)
270 w
= u_str_width(str
);
274 memset(print_buffer
, ' ', indent
);
278 d
+= u_copy_chars(print_buffer
+ d
, str
, &width
);
279 print_buffer
[d
++] = '.';
280 print_buffer
[d
++] = '.';
281 print_buffer
[d
++] = '.';
286 print_buffer
[d
++] = str
[s
++];
288 memset(print_buffer
+ d
, ' ', width
- w
);
291 print_buffer
[d
++] = ' ';
292 print_buffer
[d
++] = 0;
293 dump_print_buffer(row
, col
);
296 static void sprint_ascii(int row
, int col
, const char *str
, int len
)
303 print_buffer
[0] = ' ';
305 memcpy(print_buffer
+ 1, str
, len
- 3);
306 print_buffer
[len
- 2] = '.';
307 print_buffer
[len
- 1] = '.';
308 print_buffer
[len
- 0] = '.';
310 memcpy(print_buffer
+ 1, str
, l
);
311 memset(print_buffer
+ 1 + l
, ' ', len
- l
);
313 print_buffer
[len
+ 1] = ' ';
314 print_buffer
[len
+ 2] = 0;
315 mvaddstr(row
, col
, print_buffer
);
318 static void print_tree(struct window
*win
, int row
, struct iter
*iter
)
320 const char *noname
= "<no name>";
321 struct artist
*artist
;
324 int current
, selected
, active
;
326 artist
= iter_to_artist(iter
);
327 album
= iter_to_album(iter
);
329 current
= playlist
.cur_album
== album
;
331 current
= playlist
.cur_artist
== artist
;
333 window_get_sel(win
, &sel
);
334 selected
= iters_equal(iter
, &sel
);
335 active
= playlist
.cur_win
== TREE_WIN
;
336 bkgdset(cursed_colors
[(active
<< 2) | (selected
<< 1) | current
]);
338 sprint(tree_win_y
+ row
+ 1, tree_win_x
, album
->name
? : noname
, tree_win_w
, 2);
340 sprint(tree_win_y
+ row
+ 1, tree_win_x
, artist
->name
? : noname
, tree_win_w
, 0);
358 /* artist, album, disc number, track number, title, year, duration, path+filename, filename */
359 static struct format_option track_fopts
[NR_TFS
+ 1] = {
360 { { 0 }, 0, FO_STR
, 'a' },
361 { { 0 }, 0, FO_STR
, 'l' },
362 { { 0 }, 0, FO_INT
, 'D' },
363 { { 0 }, 0, FO_INT
, 'n' },
364 { { 0 }, 0, FO_STR
, 't' },
365 { { 0 }, 0, FO_STR
, 'y' },
366 { { 0 }, 0, FO_STR
, 'g' },
367 { { 0 }, 0, FO_TIME
, 'd' },
368 { { 0 }, 0, FO_STR
, 'f' },
369 { { 0 }, 0, FO_STR
, 'F' },
373 static inline void fopt_set_str(struct format_option
*fopt
, const char *str
)
375 BUG_ON(fopt
->type
!= FO_STR
);
377 fopt
->u
.fo_str
= str
;
384 static inline void fopt_set_int(struct format_option
*fopt
, int value
, int empty
)
386 BUG_ON(fopt
->type
!= FO_INT
);
387 fopt
->u
.fo_int
= value
;
391 static inline void fopt_set_time(struct format_option
*fopt
, int value
, int empty
)
393 BUG_ON(fopt
->type
!= FO_TIME
);
394 fopt
->u
.fo_time
= value
;
398 static void fill_track_fopts(struct track
*track
)
403 filename
= track
->info
->filename
;
405 utf8_encode(track
->info
->filename
);
406 filename
= conv_buffer
;
408 fopt_set_str(&track_fopts
[TF_ARTIST
], track
->album
->artist
->name
);
409 fopt_set_str(&track_fopts
[TF_ALBUM
], track
->album
->name
);
410 fopt_set_int(&track_fopts
[TF_DISC
], track
->disc
, track
->disc
== -1);
411 fopt_set_int(&track_fopts
[TF_TRACK
], track
->num
, track
->num
== -1);
412 fopt_set_str(&track_fopts
[TF_TITLE
], track
->name
);
413 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(track
->info
->comments
, "date"));
414 fopt_set_str(&track_fopts
[TF_GENRE
], comments_get_val(track
->info
->comments
, "genre"));
415 fopt_set_time(&track_fopts
[TF_DURATION
], track
->info
->duration
, track
->info
->duration
== -1);
416 fopt_set_str(&track_fopts
[TF_PATHFILE
], filename
);
418 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
422 f
= strrchr(filename
, '/');
424 fopt_set_str(&track_fopts
[TF_FILE
], f
+ 1);
426 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
431 static void fill_track_fopts_track_info(struct track_info
*info
)
437 filename
= info
->filename
;
439 utf8_encode(info
->filename
);
440 filename
= conv_buffer
;
442 disc
= comments_get_int(info
->comments
, "discnumber");
443 num
= comments_get_int(info
->comments
, "tracknumber");
445 fopt_set_str(&track_fopts
[TF_ARTIST
], comments_get_val(info
->comments
, "artist"));
446 fopt_set_str(&track_fopts
[TF_ALBUM
], comments_get_val(info
->comments
, "album"));
447 fopt_set_int(&track_fopts
[TF_DISC
], disc
, disc
== -1);
448 fopt_set_int(&track_fopts
[TF_TRACK
], num
, num
== -1);
449 fopt_set_str(&track_fopts
[TF_TITLE
], comments_get_val(info
->comments
, "title"));
450 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(info
->comments
, "date"));
451 fopt_set_str(&track_fopts
[TF_GENRE
], comments_get_val(info
->comments
, "genre"));
452 fopt_set_time(&track_fopts
[TF_DURATION
], info
->duration
, info
->duration
== -1);
453 fopt_set_str(&track_fopts
[TF_PATHFILE
], filename
);
454 if (is_url(info
->filename
)) {
455 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
459 f
= strrchr(filename
, '/');
461 fopt_set_str(&track_fopts
[TF_FILE
], f
+ 1);
463 fopt_set_str(&track_fopts
[TF_FILE
], filename
);
468 static void print_track(struct window
*win
, int row
, struct iter
*iter
)
472 int current
, selected
, active
;
474 track
= iter_to_track(iter
);
475 current
= playlist
.cur_track
== track
;
476 window_get_sel(win
, &sel
);
477 selected
= iters_equal(iter
, &sel
);
478 active
= playlist
.cur_win
== TRACK_WIN
;
479 bkgdset(cursed_colors
[(active
<< 2) | (selected
<< 1) | current
]);
480 fill_track_fopts(track
);
481 if (track_info_has_tag(track
->info
)) {
482 format_print(print_buffer
, print_buffer_size
, track_win_w
, track_win_format
, track_fopts
);
484 format_print(print_buffer
, print_buffer_size
, track_win_w
, track_win_alt_format
, track_fopts
);
486 dump_print_buffer(track_win_y
+ row
+ 1, track_win_x
);
489 static void print_shuffle(struct window
*win
, int row
, struct iter
*iter
)
493 int current
, selected
, active
;
495 track
= iter_to_shuffle_track(iter
);
496 current
= playlist
.cur_track
== track
;
497 window_get_sel(win
, &sel
);
498 selected
= iters_equal(iter
, &sel
);
499 active
= playlist
.cur_win
== SHUFFLE_WIN
;
500 bkgdset(cursed_colors
[(active
<< 2) | (selected
<< 1) | current
]);
501 fill_track_fopts(track
);
502 if (track_info_has_tag(track
->info
)) {
503 format_print(print_buffer
, print_buffer_size
, COLS
, list_win_format
, track_fopts
);
505 format_print(print_buffer
, print_buffer_size
, COLS
, list_win_alt_format
, track_fopts
);
507 dump_print_buffer(row
+ 1, 0);
510 static void print_sorted(struct window
*win
, int row
, struct iter
*iter
)
514 int current
, selected
, active
;
516 track
= iter_to_sorted_track(iter
);
517 current
= playlist
.cur_track
== track
;
518 window_get_sel(win
, &sel
);
519 selected
= iters_equal(iter
, &sel
);
520 active
= playlist
.cur_win
== SORTED_WIN
;
521 bkgdset(cursed_colors
[(active
<< 2) | (selected
<< 1) | current
]);
522 fill_track_fopts(track
);
523 if (track_info_has_tag(track
->info
)) {
524 format_print(print_buffer
, print_buffer_size
, COLS
, list_win_format
, track_fopts
);
526 format_print(print_buffer
, print_buffer_size
, COLS
, list_win_alt_format
, track_fopts
);
528 dump_print_buffer(row
+ 1, 0);
531 static void print_play_queue(struct window
*win
, int row
, struct iter
*iter
)
533 struct play_queue_entry
*e
;
534 struct track_info
*info
;
536 int current
, selected
, active
;
538 e
= iter_to_play_queue_entry(iter
);
539 info
= e
->track_info
;
541 window_get_sel(win
, &sel
);
543 selected
= iters_equal(iter
, &sel
);
545 bkgdset(cursed_colors
[(active
<< 2) | (selected
<< 1) | current
]);
547 fill_track_fopts_track_info(info
);
549 if (track_info_has_tag(info
)) {
550 format_print(print_buffer
, print_buffer_size
, COLS
, list_win_format
, track_fopts
);
552 format_print(print_buffer
, print_buffer_size
, COLS
, list_win_alt_format
, track_fopts
);
554 dump_print_buffer(row
+ 1, 0);
557 static void print_browser(struct window
*win
, int row
, struct iter
*iter
)
559 struct browser_entry
*e
;
563 e
= iter_to_browser_entry(iter
);
564 window_get_sel(win
, &sel
);
565 selected
= iters_equal(iter
, &sel
);
570 bkgdset(cursed_colors
[(active
<< 2) | (selected
<< 1) | current
]);
572 if (e
->type
== BROWSER_ENTRY_DIR
) {
573 bkgdset(cursed_colors
[COLOR_BROWSER_DIR
]);
575 bkgdset(cursed_colors
[COLOR_BROWSER_FILE
]);
579 /* file name encoding == terminal encoding. no need to convert */
581 sprint(row
+ 1, 0, e
->name
, COLS
, 0);
583 sprint_ascii(row
+ 1, 0, e
->name
, COLS
);
587 static void print_filter(struct window
*win
, int row
, struct iter
*iter
)
590 struct filter_entry
*e
= iter_to_filter_entry(iter
);
596 /* is the filter currently active? */
597 int current
= e
->active
;
599 window_get_sel(win
, &sel
);
600 selected
= iters_equal(iter
, &sel
);
601 bkgdset(cursed_colors
[(active
<< 2) | (selected
<< 1) | current
]);
603 snprintf(buf
, sizeof(buf
), "%c %-15s %s", e
->selected
? '*' : ' ', e
->name
, e
->filter
);
604 sprint(row
+ 1, 0, buf
, COLS
, 0);
607 static void update_window(struct window
*win
, int x
, int y
, int w
, const char *title
,
608 void (*print
)(struct window
*, int, struct iter
*))
614 bkgdset(cursed_colors
[COLOR_TITLE
]);
615 c
= snprintf(print_buffer
, w
+ 1, " %s", title
);
618 memset(print_buffer
+ c
, ' ', w
- c
+ 1);
620 dump_print_buffer(y
, x
);
621 nr_rows
= window_get_nr_rows(win
);
623 if (window_get_top(win
, &iter
)) {
624 while (i
< nr_rows
) {
625 print(win
, i
, &iter
);
627 if (!window_get_next(win
, &iter
))
632 bkgdset(cursed_colors
[0]);
633 memset(print_buffer
, ' ', w
);
635 while (i
< nr_rows
) {
636 dump_print_buffer(y
+ i
+ 1, x
);
641 static void update_tree_window(void)
643 playlist
.tree_win_changed
= 0;
644 update_window(playlist
.tree_win
, tree_win_x
, tree_win_y
,
645 tree_win_w
, "Artist / Album", print_tree
);
648 static void update_track_window(void)
652 format_print(title
, sizeof(title
), track_win_w
- 2, "Track%=Press F1 for Help", track_fopts
);
653 playlist
.track_win_changed
= 0;
654 update_window(playlist
.track_win
, track_win_x
, track_win_y
,
655 track_win_w
, title
, print_track
);
658 static void update_shuffle_window(void)
663 filename
= playlist_filename
? playlist_filename
: playlist_autosave_filename
;
667 utf8_encode(filename
);
668 filename
= conv_buffer
;
670 snprintf(title
, sizeof(title
), "Shuffle List - %s", filename
);
671 playlist
.shuffle_win_changed
= 0;
672 update_window(playlist
.shuffle_win
, 0, 0, COLS
, title
, print_shuffle
);
675 static void update_sorted_window(void)
680 filename
= playlist_filename
? playlist_filename
: playlist_autosave_filename
;
684 utf8_encode(filename
);
685 filename
= conv_buffer
;
687 snprintf(title
, sizeof(title
), "Sorted by '%s' - %s", sort_string
, filename
);
688 playlist
.sorted_win_changed
= 0;
689 update_window(playlist
.sorted_win
, 0, 0, COLS
, title
, print_sorted
);
692 static void update_play_queue_window(void)
694 play_queue_changed
= 0;
695 update_window(play_queue_win
, 0, 0, COLS
, "Play Queue", print_play_queue
);
698 static void update_browser_window(void)
706 dirname
= browser_dir
;
708 utf8_encode(browser_dir
);
709 dirname
= conv_buffer
;
711 snprintf(title
, sizeof(title
), "Directory Browser - %s", dirname
);
712 update_window(browser_win
, 0, 0, COLS
, title
, print_browser
);
715 static void update_filters_window(void)
718 update_window(filters_win
, 0, 0, COLS
, "Filters", print_filter
);
721 static void draw_separator(void)
725 bkgdset(cursed_colors
[COLOR_TITLE
]);
726 mvaddch(0, tree_win_w
, ' ');
727 bkgdset(cursed_colors
[COLOR_SEPARATOR
]);
728 for (row
= 1; row
< LINES
- 3; row
++)
729 mvaddch(row
, tree_win_w
, ACS_VLINE
);
732 static void update_view(void)
734 switch (ui_curses_view
) {
737 update_tree_window();
738 update_track_window();
744 update_shuffle_window();
749 update_sorted_window();
752 case PLAY_QUEUE_VIEW
:
754 update_play_queue_window();
758 update_browser_window();
761 update_filters_window();
782 /* status, position, duration, total duration, volume, left vol, right vol, buf,
783 * repeat, continue, playmode, playlist_mode */
784 static struct format_option status_fopts
[NR_SFS
+ 1] = {
785 { { 0 }, 0, FO_STR
, 's' },
786 { { 0 }, 0, FO_TIME
, 'p' },
787 { { 0 }, 0, FO_TIME
, 'd' },
788 { { 0 }, 0, FO_TIME
, 't' },
789 { { 0 }, 0, FO_INT
, 'v' },
790 { { 0 }, 0, FO_INT
, 'l' },
791 { { 0 }, 0, FO_INT
, 'r' },
792 { { 0 }, 0, FO_INT
, 'b' },
793 { { 0 }, 0, FO_STR
, 'R' },
794 { { 0 }, 0, FO_STR
, 'C' },
795 { { 0 }, 0, FO_STR
, 'P' },
796 { { 0 }, 0, FO_STR
, 'L' },
800 static void update_statusline(void)
802 static char *status_strs
[] = { ".", ">", "|" };
803 static char *playlist_mode_strs
[] = {
804 "all", "artist", "album"
806 static char *play_mode_strs
[] = {
807 "tree", "shuffle", "sorted"
809 enum playlist_mode playlist_mode
;
810 enum play_mode play_mode
;
811 int volume
, buffer_fill
;
812 int total_time
, repeat
;
817 pl_get_status(&repeat
, &playlist_mode
, &play_mode
, &total_time
);
819 duration
= cur_track_info
->duration
;
823 volume
= (player_info
.vol_left
+ player_info
.vol_right
) / 2.0 + 0.5;
824 buffer_fill
= (double)player_info
.buffer_fill
/ (double)player_info
.buffer_size
* 100.0 + 0.5;
826 fopt_set_str(&status_fopts
[SF_STATUS
], status_strs
[player_info
.status
]);
828 if (show_remaining_time
&& duration
!= -1) {
829 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
- duration
, 0);
831 fopt_set_time(&status_fopts
[SF_POSITION
], player_info
.pos
, 0);
834 fopt_set_time(&status_fopts
[SF_DURATION
], duration
, 0);
835 fopt_set_time(&status_fopts
[SF_TOTAL
], total_time
, 0);
836 fopt_set_int(&status_fopts
[SF_VOLUME
], volume
, 0);
837 fopt_set_int(&status_fopts
[SF_LVOLUME
], player_info
.vol_left
, 0);
838 fopt_set_int(&status_fopts
[SF_RVOLUME
], player_info
.vol_right
, 0);
839 fopt_set_int(&status_fopts
[SF_BUFFER
], buffer_fill
, 0);
840 fopt_set_str(&status_fopts
[SF_REPEAT
], repeat
? "rep" : "");
841 fopt_set_str(&status_fopts
[SF_CONTINUE
], player_info
.cont
? "cont" : "");
842 fopt_set_str(&status_fopts
[SF_PLAYMODE
], play_mode_strs
[play_mode
]);
843 fopt_set_str(&status_fopts
[SF_PLAYLISTMODE
], playlist_mode_strs
[playlist_mode
]);
845 strcpy(format
, " %s %p ");
847 strcat(format
, "/ %d ");
848 if (player_info
.vol_left
!= player_info
.vol_right
) {
849 strcat(format
, "- %t vol: %l,%r ");
851 strcat(format
, "- %t vol: %v ");
853 if (cur_track_info
&& is_url(cur_track_info
->filename
))
854 strcat(format
, "buf: %b ");
855 strcat(format
, "%=%3R | %4C | %-7P | %-6L ");
856 format_print(print_buffer
, print_buffer_size
, COLS
, format
, status_fopts
);
858 msg
= player_info
.error_msg
;
859 player_info
.error_msg
= NULL
;
861 player_info_unlock();
863 bkgdset(cursed_colors
[COLOR_STATUSLINE
]);
864 dump_print_buffer(LINES
- 2, 0);
867 ui_curses_display_error_msg("%s", msg
);
872 static void dump_buffer(const char *buffer
)
882 static void update_commandline(void)
889 bkgdset(cursed_colors
[COLOR_ERROR
]);
894 bkgdset(cursed_colors
[COLOR_COMMANDLINE
]);
895 if (ui_curses_input_mode
== NORMAL_MODE
) {
900 /* COMMAND_MODE or SEARCH_MODE */
901 w
= u_str_width(cmdline
.line
);
903 if (ui_curses_input_mode
== SEARCH_MODE
)
904 ch
= search_direction
== SEARCH_FORWARD
? '/' : '?';
908 dump_buffer(cmdline
.line
);
911 /* keep cursor as far right as possible */
912 int skip
, width
, cw
, idx
;
914 /* cursor pos (width, not chars. doesn't count the ':') */
915 cw
= u_str_nwidth(cmdline
.line
, cmdline
.cpos
);
917 skip
= cw
+ 2 - COLS
;
922 /* skip rest (if any) */
923 idx
= u_skip_chars(cmdline
.line
, &skip
);
926 idx
= u_copy_chars(print_buffer
, cmdline
.line
+ idx
, &width
);
927 while (width
< COLS
) {
928 /* cursor is at end of the buffer
929 * print a space (or 2 if the last skipped character
932 print_buffer
[idx
++] = ' ';
935 print_buffer
[idx
] = 0;
936 dump_buffer(print_buffer
);
938 /* print ':' + COLS - 1 chars */
941 idx
= u_copy_chars(print_buffer
, cmdline
.line
, &width
);
942 print_buffer
[idx
] = 0;
943 dump_buffer(print_buffer
);
948 /* lock player_info! */
949 static const char *get_stream_title(const char *metadata
)
951 static char stream_title
[255 * 16 + 1];
954 ptr
= strstr(player_info
.metadata
, "StreamTitle='");
960 if (*ptr
== '\'' && *(ptr
+ 1) == ';') {
961 memcpy(stream_title
, title
, ptr
- title
);
962 stream_title
[ptr
- title
] = 0;
970 static void update_titleline(void)
972 bkgdset(cursed_colors
[COLOR_TITLELINE
]);
974 if (cur_track_info
) {
975 const char *filename
;
976 int use_alt_format
= 0;
977 struct keyval
*cur_comments
= cur_track_info
->comments
;
979 if (cur_comments
[0].key
== NULL
) {
980 const char *title
= get_stream_title(player_info
.metadata
);
984 fopt_set_str(&track_fopts
[TF_ARTIST
], NULL
);
985 fopt_set_str(&track_fopts
[TF_ALBUM
], NULL
);
986 fopt_set_int(&track_fopts
[TF_DISC
], -1, 1);
987 fopt_set_int(&track_fopts
[TF_TRACK
], -1, 1);
988 fopt_set_str(&track_fopts
[TF_TITLE
], title
);
989 fopt_set_str(&track_fopts
[TF_YEAR
], NULL
);
991 int disc_num
, track_num
;
993 disc_num
= comments_get_int(cur_comments
, "discnumber");
994 track_num
= comments_get_int(cur_comments
, "tracknumber");
995 fopt_set_str(&track_fopts
[TF_ARTIST
], comments_get_val(cur_comments
, "artist"));
996 fopt_set_str(&track_fopts
[TF_ALBUM
], comments_get_val(cur_comments
, "album"));
997 fopt_set_int(&track_fopts
[TF_DISC
], disc_num
, disc_num
== -1);
998 fopt_set_int(&track_fopts
[TF_TRACK
], track_num
, track_num
== -1);
999 fopt_set_str(&track_fopts
[TF_TITLE
], comments_get_val(cur_comments
, "title"));
1000 fopt_set_str(&track_fopts
[TF_YEAR
], comments_get_val(cur_comments
, "date"));
1002 fopt_set_time(&track_fopts
[TF_DURATION
], cur_track_info
->duration
, cur_track_info
->duration
== -1);
1003 fopt_set_str(&track_fopts
[TF_PATHFILE
], cur_track_info
->filename
);
1004 if (is_url(cur_track_info
->filename
)) {
1005 fopt_set_str(&track_fopts
[TF_FILE
], cur_track_info
->filename
);
1007 filename
= strrchr(cur_track_info
->filename
, '/');
1009 fopt_set_str(&track_fopts
[TF_FILE
], filename
+ 1);
1011 fopt_set_str(&track_fopts
[TF_FILE
], cur_track_info
->filename
);
1014 if (use_alt_format
) {
1015 format_print(print_buffer
, print_buffer_size
, COLS
, current_alt_format
, track_fopts
);
1017 format_print(print_buffer
, print_buffer_size
, COLS
, current_format
, track_fopts
);
1019 dump_print_buffer(LINES
- 3, 0);
1021 if (update_window_title
) {
1025 if (use_alt_format
) {
1026 format_print(print_buffer
, print_buffer_size
, sizeof(print_buffer
) - 1, window_title_alt_format
, track_fopts
);
1028 format_print(print_buffer
, print_buffer_size
, sizeof(print_buffer
) - 1, window_title_format
, track_fopts
);
1031 /* remove whitespace */
1032 i
= sizeof(print_buffer
) - 2;
1033 while (i
> 0 && print_buffer
[i
] == ' ')
1035 print_buffer
[i
+ 1] = 0;
1038 wtitle
= print_buffer
;
1040 utf8_decode(print_buffer
);
1041 wtitle
= conv_buffer
;
1044 printf("\033]0;%s\007", wtitle
);
1051 if (update_window_title
) {
1052 printf("\033]0;CMus " VERSION
"\007");
1056 player_info_unlock();
1059 static int cmdline_cursor_column(void)
1063 /* width of the text in the buffer before cursor */
1064 cw
= u_str_nwidth(cmdline
.line
, cmdline
.cpos
);
1066 if (1 + cw
< COLS
) {
1067 /* whole line is visible */
1071 /* beginning of cmdline is not visible */
1073 /* check if the first visible char in cmdline would be halved
1074 * double-width character which is not possible. we need to skip the
1075 * whole character and move cursor to COLS - 2 column. */
1076 skip
= cw
+ 2 - COLS
;
1083 u_skip_chars(cmdline
.line
, &s
);
1085 /* the last skipped char was double-width */
1091 static void post_update(void)
1093 /* refresh makes cursor visible at least for urxvt */
1094 if (ui_curses_input_mode
== COMMAND_MODE
|| ui_curses_input_mode
== SEARCH_MODE
) {
1095 move(LINES
- 1, cmdline_cursor_column());
1105 void ui_curses_update_titleline(void)
1112 static void ui_curses_update_commandline(void)
1115 update_commandline();
1119 static void ui_curses_update_statusline(void)
1122 update_statusline();
1126 void ui_curses_update_view(void)
1133 void ui_curses_display_info_msg(const char *format
, ...)
1137 va_start(ap
, format
);
1138 vsnprintf(error_msg
, sizeof(error_msg
), format
, ap
);
1141 ui_curses_update_commandline();
1144 void ui_curses_display_error_msg(const char *format
, ...)
1148 strcpy(error_msg
, "Error: ");
1149 va_start(ap
, format
);
1150 vsnprintf(error_msg
+ 7, sizeof(error_msg
) - 7, format
, ap
);
1153 error_time
= time(NULL
);
1154 ui_curses_update_commandline();
1157 int ui_curses_yes_no_query(const char *format
, ...)
1163 va_start(ap
, format
);
1164 vsnprintf(buffer
, sizeof(buffer
), format
, ap
);
1168 bkgdset(cursed_colors
[COLOR_INFO
]);
1170 /* no need to convert buffer.
1171 * it is always encoded in the right charset (assuming filenames are
1172 * encoded in same charset as LC_CTYPE).
1182 if (ch
== ERR
|| ch
== 0)
1188 ui_curses_update_commandline();
1192 void ui_curses_search_not_found(void)
1194 const char *what
= "Track";
1196 if (search_restricted
) {
1197 switch (ui_curses_view
) {
1199 what
= "Artist/album";
1203 case PLAY_QUEUE_VIEW
:
1207 what
= "File/Directory";
1214 switch (ui_curses_view
) {
1218 case PLAY_QUEUE_VIEW
:
1222 what
= "File/Directory";
1229 ui_curses_display_info_msg("%s not found: %s", what
, search_str
? : "");
1232 static void set_view(int view
)
1234 if (view
== ui_curses_view
)
1236 ui_curses_view
= view
;
1240 __pl_set_view(view
);
1241 switch (ui_curses_view
) {
1243 searchable
= tree_searchable
;
1244 update_tree_window();
1245 update_track_window();
1249 searchable
= shuffle_searchable
;
1250 update_shuffle_window();
1253 searchable
= sorted_searchable
;
1254 update_sorted_window();
1256 case PLAY_QUEUE_VIEW
:
1257 searchable
= play_queue_searchable
;
1258 update_play_queue_window();
1261 searchable
= browser_searchable
;
1262 update_browser_window();
1265 searchable
= filters_searchable
;
1266 update_filters_window();
1274 void ui_curses_toggle_remaining_time(void)
1276 show_remaining_time
^= 1;
1277 ui_curses_update_statusline();
1280 void ui_curses_tree_view(void)
1282 set_view(TREE_VIEW
);
1285 void ui_curses_shuffle_view(void)
1287 set_view(SHUFFLE_VIEW
);
1290 void ui_curses_sorted_view(void)
1292 set_view(SORTED_VIEW
);
1295 void ui_curses_play_queue_view(void)
1297 set_view(PLAY_QUEUE_VIEW
);
1300 void ui_curses_browser_view(void)
1302 set_view(BROWSER_VIEW
);
1305 void ui_curses_filters_view(void)
1307 set_view(FILTERS_VIEW
);
1310 void ui_curses_command_mode(void)
1314 ui_curses_input_mode
= COMMAND_MODE
;
1315 ui_curses_update_commandline();
1318 void ui_curses_search_mode(void)
1322 ui_curses_input_mode
= SEARCH_MODE
;
1323 search_direction
= SEARCH_FORWARD
;
1324 ui_curses_update_commandline();
1327 void ui_curses_search_backward_mode(void)
1331 ui_curses_input_mode
= SEARCH_MODE
;
1332 search_direction
= SEARCH_BACKWARD
;
1333 ui_curses_update_commandline();
1336 void ui_curses_quit(void)
1341 void ui_curses_update_color(int idx
)
1343 /* first color pair is 1 */
1347 fg
= clamp(fg_colors
[idx
], -1, 255);
1348 bg
= clamp(bg_colors
[idx
], -1, 255);
1350 init_pair(pair
, fg
, bg
);
1351 cursed
= COLOR_PAIR(pair
);
1353 if (fg
>= 8 && fg
<= 15) {
1354 /* fg colors 8..15 are special (0..7 + bold) */
1355 init_pair(pair
, fg
& 7, bg
);
1356 cursed
= COLOR_PAIR(pair
) | (fg
& BRIGHT
? A_BOLD
: 0);
1358 init_pair(pair
, fg
, bg
);
1359 cursed
= COLOR_PAIR(pair
);
1362 cursed_colors
[idx
] = cursed
;
1365 static void full_update(void)
1370 update_statusline();
1371 update_commandline();
1375 void ui_curses_set_sort(const char *value
, int warn
)
1377 static const char *valid
[] = {
1391 keys
= bsplit(value
, strlen(value
), ',', 0);
1392 if (keys
[0] != NULL
&& keys
[0][0] == 0 && keys
[1] == NULL
) {
1397 for (i
= 0; keys
[i
]; i
++) {
1398 for (j
= 0; valid
[j
]; j
++) {
1399 if (strcmp(keys
[i
], valid
[j
]) == 0)
1402 if (valid
[j
] == NULL
) {
1404 ui_curses_display_error_msg("invalid sort key '%s'", keys
[i
]);
1405 free_str_array(keys
);
1409 pl_set_sort_keys(keys
);
1411 sort_string
= xstrdup(value
);
1414 #define HELP_WIDTH 80
1415 #define HELP_HEIGHT 23
1416 #define HELP_W (HELP_WIDTH - 2)
1417 #define HELP_H (HELP_HEIGHT - 2)
1419 static void display_global_help(WINDOW
*w
)
1425 mvwaddstr(w
, row
++, (HELP_W
- 11) / 2, "Global Keys");
1426 mvwaddstr(w
, row
++, (HELP_W
- 11) / 2, "~~~~~~~~~~~");
1428 mvwaddstr(w
, row
++, col
, "z - skip back in playlist");
1429 mvwaddstr(w
, row
++, col
, "x - play");
1430 mvwaddstr(w
, row
++, col
, "c - pause");
1431 mvwaddstr(w
, row
++, col
, "v - stop");
1432 mvwaddstr(w
, row
++, col
, "b - skip forward in playlist");
1433 mvwaddstr(w
, row
++, col
, "C - toggle continue");
1434 mvwaddstr(w
, row
++, col
, "r - toggle repeat");
1435 mvwaddstr(w
, row
++, col
, "m - toggle playlist mode");
1436 mvwaddstr(w
, row
++, col
, "p - toggle play mode");
1437 mvwaddstr(w
, row
++, col
, "t - toggle time elapsed/remaining");
1438 mvwaddstr(w
, row
++, col
, "Q - quit");
1439 mvwaddstr(w
, row
++, col
, ": - command mode");
1440 mvwaddstr(w
, row
++, col
, "left, h - seek 5 seconds back");
1441 mvwaddstr(w
, row
++, col
, "right, l - seek 5 seconds forward");
1445 mvwaddstr(w
, row
++, col
, "1 - show artist/album/track view");
1446 mvwaddstr(w
, row
++, col
, "2 - show shuffle view");
1447 mvwaddstr(w
, row
++, col
, "3 - show sorted view");
1448 mvwaddstr(w
, row
++, col
, "4 - show play queue view");
1449 mvwaddstr(w
, row
++, col
, "5 - show directory browser");
1450 mvwaddstr(w
, row
++, col
, "6 - show filter view");
1451 mvwaddstr(w
, row
++, col
, "up, k - move up");
1452 mvwaddstr(w
, row
++, col
, "down, j - move down");
1453 mvwaddstr(w
, row
++, col
, "page up, ctrl-b - move page up");
1454 mvwaddstr(w
, row
++, col
, "page down, ctrl-f - move page down");
1455 mvwaddstr(w
, row
++, col
, "home, g - goto top");
1456 mvwaddstr(w
, row
++, col
, "end, G - goto bottom");
1457 mvwaddstr(w
, row
++, col
, "- or +, = - volume down or up");
1458 mvwaddstr(w
, row
++, col
, "{ or } - left or right channel down");
1459 mvwaddstr(w
, row
++, col
, "[ or ] - left or right channel up");
1462 static void display_misc_help(WINDOW
*w
)
1468 mvwaddstr(w
, row
++, col
, "Tree / Shuffle / Sorted View Keys");
1469 mvwaddstr(w
, row
++, col
, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
1471 mvwaddstr(w
, row
++, col
, "D, del - remove selection");
1472 mvwaddstr(w
, row
++, col
, "e - append to play queue");
1473 mvwaddstr(w
, row
++, col
, "E - prepend to play queue");
1474 mvwaddstr(w
, row
++, col
, "i - jump to current track");
1475 mvwaddstr(w
, row
++, col
, "u - update tags");
1476 mvwaddstr(w
, row
++, col
, "enter - play selected track");
1477 mvwaddstr(w
, row
++, col
, "space - show/hide albums");
1478 mvwaddstr(w
, row
++, col
, "tab - switch tree/track windows");
1480 mvwaddstr(w
, row
++, col
, "Play Queue Keys");
1481 mvwaddstr(w
, row
++, col
, "~~~~~~~~~~~~~~~");
1483 mvwaddstr(w
, row
++, col
, "D, del - remove selected track");
1487 mvwaddstr(w
, row
++, col
, "Directory Browser Keys");
1488 mvwaddstr(w
, row
++, col
, "~~~~~~~~~~~~~~~~~~~~~~");
1490 mvwaddstr(w
, row
++, col
, "D, del - remove selected file");
1491 mvwaddstr(w
, row
++, col
, "a - add to playlist");
1492 mvwaddstr(w
, row
++, col
, "e - append to play queue");
1493 mvwaddstr(w
, row
++, col
, "E - prepend to play queue");
1494 mvwaddstr(w
, row
++, col
, "i - show/hide hidden files");
1495 mvwaddstr(w
, row
++, col
, "u - update dir/playlist");
1496 mvwaddstr(w
, row
++, col
, "enter - cd to dir/playlist");
1497 mvwaddstr(w
, row
++, col
, " or play file");
1498 mvwaddstr(w
, row
++, col
, "backspace - cd to parent directory");
1500 mvwaddstr(w
, row
++, col
, "Filter View Keys");
1501 mvwaddstr(w
, row
++, col
, "~~~~~~~~~~~~~~~~");
1503 mvwaddstr(w
, row
++, col
, "D, del - remove selected filter");
1504 mvwaddstr(w
, row
++, col
, "space - select/unselect filter");
1505 mvwaddstr(w
, row
++, col
, "enter - apply selected filters");
1508 static void display_search_mode_help(WINDOW
*w
)
1514 mvwaddstr(w
, row
++, (HELP_W
- 9) / 2, "Searching");
1515 mvwaddstr(w
, row
++, (HELP_W
- 9) / 2, "~~~~~~~~~");
1517 mvwaddstr(w
, row
++, col
, "/WORDS - search forward");
1518 mvwaddstr(w
, row
++, col
, "?WORDS - search backward");
1519 mvwaddstr(w
, row
++, col
, "//WORDS - search forward (see below)");
1520 mvwaddstr(w
, row
++, col
, "??WORDS - search backward (see below)");
1521 mvwaddstr(w
, row
++, col
, "/ - search forward for the latest used pattern");
1522 mvwaddstr(w
, row
++, col
, "? - search backward for the latest used pattern");
1523 mvwaddstr(w
, row
++, col
, "n - search next");
1524 mvwaddstr(w
, row
++, col
, "N - search previous");
1526 mvwaddstr(w
, row
++, col
, "WORDS is list of words separated by spaces. Search is case insensitive");
1527 mvwaddstr(w
, row
++, col
, "and works in every view.");
1529 mvwaddstr(w
, row
++, col
, "In views 1-4 words are compared to artist, album and title tags. Use");
1530 mvwaddstr(w
, row
++, col
, "//WORDS and ??WORDS to search only artists/albums in view 1 or titles in");
1531 mvwaddstr(w
, row
++, col
, "views 2-4. If the file doesn't have tags words are compared to filename");
1532 mvwaddstr(w
, row
++, col
, "without path. In view 5 words are compared to filename without path.");
1535 static void display_command_mode_help(WINDOW
*w
)
1541 mvwaddstr(w
, row
++, (HELP_W
- 12) / 2, "Command Mode");
1542 mvwaddstr(w
, row
++, (HELP_W
- 12) / 2, "~~~~~~~~~~~~");
1544 mvwaddstr(w
, row
++, col
, ":add file/dir/playlist - add to playlist");
1545 mvwaddstr(w
, row
++, col
, ":cd [dir] - change directory");
1546 mvwaddstr(w
, row
++, col
, ":clear - clear playlist");
1547 mvwaddstr(w
, row
++, col
, ":enqueue file/dir/playlist - add to play queue");
1548 mvwaddstr(w
, row
++, col
, ":filter [value] - set temporary playlist filter");
1549 mvwaddstr(w
, row
++, col
, ":fset name=value - add or replace filter");
1550 mvwaddstr(w
, row
++, col
, ":load filename - load playlist");
1551 mvwaddstr(w
, row
++, col
, ":run command - run `command' for the selected files");
1552 mvwaddstr(w
, row
++, col
, ":save [filename] - save playlist");
1553 mvwaddstr(w
, row
++, col
, ":seek POS[mh] - seek to absolute position");
1554 mvwaddstr(w
, row
++, col
, " POS is seconds, minutes (m) or hours (h)");
1555 mvwaddstr(w
, row
++, col
, ":seek [+-]POS[mh] - seek to relative position");
1556 mvwaddstr(w
, row
++, col
, ":set option=value - see next page");
1557 mvwaddstr(w
, row
++, col
, ":shuffle - reshuffle playlist");
1559 mvwaddstr(w
, row
++, col
, "Use <tab> to expand commands, options, files and directories.");
1560 mvwaddstr(w
, row
++, col
, "Unambiguous short commands work too (f.e: ':a file.ogg').");
1563 static void display_options_help(WINDOW
*w
)
1569 mvwaddstr(w
, row
++, (HELP_W
- 7) / 2, "Options");
1570 mvwaddstr(w
, row
++, (HELP_W
- 7) / 2, "~~~~~~~");
1572 mvwaddstr(w
, row
++, col
, "output_plugin - output plugin (alsa, arts, oss)");
1573 mvwaddstr(w
, row
++, col
, "buffer_seconds - size of player buffer in seconds (1-10)");
1574 mvwaddstr(w
, row
++, col
, "confirm_run - confirm :run with >1 files (true/false)");
1575 mvwaddstr(w
, row
++, col
, "dsp.*, mixer.* - output plugin options");
1576 mvwaddstr(w
, row
++, col
, "color_* - user interface colors");
1577 mvwaddstr(w
, row
++, col
, "format_current - format of the line showing currently played track");
1578 mvwaddstr(w
, row
++, col
, "format_playlist - format of text in shuffle and sorted windows");
1579 mvwaddstr(w
, row
++, col
, "format_title - format of window title");
1580 mvwaddstr(w
, row
++, col
, "format_track_win - format of text in track window");
1581 mvwaddstr(w
, row
++, col
, "altformat_* - format strings used when file has no tags");
1582 mvwaddstr(w
, row
++, col
, "sort - comma separated list of sort keys for the sorted");
1583 mvwaddstr(w
, row
++, col
, " view (3). Valid keys: artist, album, title,");
1584 mvwaddstr(w
, row
++, col
, " tracknumber, discnumber, date, genre, filename)");
1585 mvwaddstr(w
, row
++, col
, "status_display_program - script to run when player status changes");
1587 mvwaddstr(w
, row
++, col
, "Example: :set sort=genre,date");
1588 mvwaddstr(w
, row
++, col
, "Use <tab> to cycle through all options.");
1591 static void display_last_help(WINDOW
*w
)
1593 const char *title
= PACKAGE
" " VERSION
;
1595 int title_len
= strlen(title
);
1598 for (i
= 0; i
< title_len
; i
++)
1603 mvwaddstr(w
, row
++, (HELP_W
- title_len
) / 2, title
);
1604 mvwaddstr(w
, row
++, (HELP_W
- title_len
) / 2, underline
);
1606 mvwaddstr(w
, row
++, col
, "Run `cmus --help' to display command line options.");
1607 mvwaddstr(w
, row
++, col
, "Full documentation: " DATADIR
"/cmus/doc/cmus.html");
1609 mvwaddstr(w
, row
++, col
, "Copyright 2004-2005 Timo Hirvonen");
1610 mvwaddstr(w
, row
++, col
, "Send bug reports, patches etc. to " PACKAGE_BUGREPORT
);
1613 void display_help(void)
1618 if (COLS
< HELP_WIDTH
|| LINES
< HELP_HEIGHT
) {
1619 ui_curses_display_error_msg("window is too small to display help");
1623 y
= (COLS
- HELP_WIDTH
) / 2;
1626 x
= (LINES
- HELP_HEIGHT
) / 2;
1629 w
= newwin(HELP_HEIGHT
, HELP_WIDTH
, x
, y
);
1636 display_global_help(w
);
1639 display_misc_help(w
);
1642 display_search_mode_help(w
);
1645 display_command_mode_help(w
);
1648 display_options_help(w
);
1650 #define LAST_HELP_PAGE 5
1651 case LAST_HELP_PAGE
:
1652 display_last_help(w
);
1653 mvwaddstr(w
, HELP_H
, (HELP_W
- 57) / 2, "Press <space> for first page or <enter> to return to cmus");
1656 if (page
< LAST_HELP_PAGE
)
1657 mvwaddstr(w
, HELP_H
, (HELP_W
- 56) / 2, "Press <space> for next page or <enter> to return to cmus");
1663 if (page
== LAST_HELP_PAGE
) {
1670 if (ch
== 127 || ch
== KEY_BACKSPACE
) {
1672 page
= LAST_HELP_PAGE
;
1679 page
= LAST_HELP_PAGE
+ 1;
1683 if (page
> LAST_HELP_PAGE
)
1692 static void clear_error(void)
1694 time_t t
= time(NULL
);
1696 /* error msg is visible at least 3s */
1697 if (t
- error_time
< 3)
1703 ui_curses_update_commandline();
1707 /* screen updates }}} */
1709 static void spawn_status_program(void)
1711 static const char *status_strs
[] = { "stopped", "playing", "paused" };
1712 const char *stream_title
= NULL
;
1716 if (status_display_program
== NULL
|| status_display_program
[0] == 0)
1720 status
= player_info
.status
;
1722 stream_title
= get_stream_title(player_info
.metadata
);
1723 player_info_unlock();
1726 argv
[i
++] = xstrdup(status_display_program
);
1728 argv
[i
++] = xstrdup("status");
1729 argv
[i
++] = xstrdup(status_strs
[status
]);
1730 if (cur_track_info
) {
1731 static const char *keys
[] = { "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
};
1734 if (is_url(cur_track_info
->filename
)) {
1735 argv
[i
++] = xstrdup("url");
1736 argv
[i
++] = xstrdup(cur_track_info
->filename
);
1738 argv
[i
++] = xstrdup("title");
1739 argv
[i
++] = xstrdup(stream_title
);
1742 argv
[i
++] = xstrdup("file");
1743 argv
[i
++] = xstrdup(cur_track_info
->filename
);
1744 for (j
= 0; keys
[j
]; j
++) {
1745 const char *key
= keys
[j
];
1748 val
= comments_get_val(cur_track_info
->comments
, key
);
1750 argv
[i
++] = xstrdup(key
);
1751 argv
[i
++] = xstrdup(val
);
1757 if (spawn(argv
, &status
) == -1)
1758 ui_curses_display_error_msg("couldn't run `%s': %s", status_display_program
, strerror(errno
));
1759 for (i
= 0; argv
[i
]; i
++)
1763 static void finish(int sig
)
1768 static int needs_to_resize
= 1;
1770 static void sig_winch(int sig
)
1772 needs_to_resize
= 1;
1775 static int get_window_size(int *lines
, int *columns
)
1779 if (ioctl(0, TIOCGWINSZ
, &ws
) == -1)
1781 *columns
= ws
.ws_col
;
1786 static void resize_playlist(int w
, int h
)
1789 track_win_w
= w
- tree_win_w
- 1;
1792 if (track_win_w
< 8)
1796 track_win_x
= tree_win_w
+ 1;
1799 pl_set_tree_win_nr_rows(h
- 1);
1800 pl_set_track_win_nr_rows(h
- 1);
1801 pl_set_shuffle_win_nr_rows(h
- 1);
1802 pl_set_sorted_win_nr_rows(h
- 1);
1805 static void get_colors(void)
1810 for (i
= 0; i
< NR_COLORS
; i
++) {
1811 snprintf(buf
, sizeof(buf
), "%s_bg", color_names
[i
]);
1812 sconf_get_int_option(&sconf_head
, buf
, &bg_colors
[i
]);
1813 snprintf(buf
, sizeof(buf
), "%s_fg", color_names
[i
]);
1814 sconf_get_int_option(&sconf_head
, buf
, &fg_colors
[i
]);
1818 static void set_colors(void)
1823 for (i
= 0; i
< NR_COLORS
; i
++) {
1824 snprintf(buf
, sizeof(buf
), "%s_bg", color_names
[i
]);
1825 sconf_set_int_option(&sconf_head
, buf
, bg_colors
[i
]);
1826 snprintf(buf
, sizeof(buf
), "%s_fg", color_names
[i
]);
1827 sconf_set_int_option(&sconf_head
, buf
, fg_colors
[i
]);
1832 #if defined(CONFIG_IRMAN)
1834 static struct irman
*irman
= NULL
;
1835 static char *irman_device
= NULL
;
1836 static int irman_fd
= -1;
1839 void (*function
)(void);
1843 { player_play
, "btn_play", NULL
},
1844 { player_stop
, "btn_stop", NULL
},
1845 { player_pause
, "btn_pause", NULL
},
1846 { cmus_prev
, "btn_prev", NULL
},
1847 { cmus_next
, "btn_next", NULL
},
1848 { cmus_seek_bwd
, "btn_seek_bwd", NULL
},
1849 { cmus_seek_fwd
, "btn_seek_fwd", NULL
},
1850 { cmus_vol_up
, "btn_vol_up", NULL
},
1851 { cmus_vol_down
, "btn_vol_down", NULL
},
1852 { pl_toggle_play_mode
, "btn_play_mode", NULL
},
1853 { pl_toggle_repeat
, "btn_repeat", NULL
},
1854 { player_toggle_cont
, "btn_continue", NULL
},
1855 { NULL
, NULL
, NULL
}
1858 static void ir_read(void)
1860 unsigned char code
[IRMAN_CODE_LEN
];
1861 char text
[IRMAN_TEXT_SIZE
];
1864 rc
= irman_get_code(irman
, code
);
1866 d_print("irman_get_code: error: %s\n", strerror(errno
));
1869 irman_code_to_text(text
, code
);
1870 for (i
= 0; ir_commands
[i
].function
; i
++) {
1871 if (ir_commands
[i
].text
== NULL
)
1873 if (strcmp(ir_commands
[i
].text
, text
) == 0) {
1874 ir_commands
[i
].function();
1880 static int ir_init(void)
1884 sconf_get_str_option(&sconf_head
, "irman_device", &irman_device
);
1885 for (i
= 0; ir_commands
[i
].function
; i
++)
1886 sconf_get_str_option(&sconf_head
, ir_commands
[i
].option
, &ir_commands
[i
].text
);
1887 if (irman_device
== NULL
) {
1888 fprintf(stderr
, "%s: irman device not set (run `" PACKAGE
" --irman-config')\n",
1892 irman
= irman_open(irman_device
);
1893 if (irman
== NULL
) {
1894 fprintf(stderr
, "%s: error opening irman device `%s': %s\n",
1895 program_name
, irman_device
,
1899 irman_fd
= irman_get_fd(irman
);
1903 static void ir_exit(void)
1911 static int u_getch(uchar
*uch
, int *keyp
)
1916 int mask
= (1 << 7);
1919 if (key
== ERR
|| key
== 0)
1925 ch
= (unsigned char)key
;
1926 while (bit
> 0 && ch
& mask
) {
1936 u
= ch
& ((1 << bit
) - 1);
1940 if (key
== ERR
|| key
== 0)
1942 ch
= (unsigned char)key
;
1943 u
= (u
<< 6) | (ch
& 63);
1951 static void ui_curses_start(void)
1953 struct sigaction act
;
1957 fd_high
= remote_socket
;
1958 #if defined(CONFIG_IRMAN)
1959 if (irman_fd
> fd_high
)
1962 cmus_load_playlist(playlist_autosave_filename
);
1964 signal(SIGINT
, finish
);
1966 sigemptyset(&act
.sa_mask
);
1968 act
.sa_handler
= sig_winch
;
1969 sigaction(SIGWINCH
, &act
, NULL
);
1973 /* turn off kb buffering */
1976 keypad(stdscr
, TRUE
);
1978 /* wait max 5 * 0.1 s if there are no keys available
1979 * doesn't really matter because we use select()
1988 use_default_colors();
1989 for (i
= 0; i
< NR_COLORS
; i
++)
1990 ui_curses_update_color(i
);
1992 d_print("Number of supported colors: %d\n", COLORS
);
1995 int needs_view_update
= 0;
1996 int needs_title_update
= 0;
1997 int needs_status_update
= 0;
1998 int needs_command_update
= 0;
1999 int needs_spawn
= 0;
2004 if (needs_to_resize
) {
2008 if (get_window_size(&lines
, &columns
) == 0) {
2009 needs_to_resize
= 0;
2010 resizeterm(lines
, columns
);
2017 resize_playlist(w
, h
);
2018 window_set_nr_rows(filters_win
, h
- 1);
2019 window_set_nr_rows(browser_win
, h
- 1);
2020 window_set_nr_rows(play_queue_win
, h
- 1);
2021 needs_view_update
= 1;
2022 needs_title_update
= 1;
2023 needs_status_update
= 1;
2024 needs_command_update
= 1;
2031 needs_spawn
= player_info
.status_changed
|| player_info
.file_changed
|| player_info
.metadata_changed
;
2033 if (player_info
.file_changed
) {
2035 track_info_unref(cur_track_info
);
2036 if (player_info
.filename
[0] == 0) {
2037 cur_track_info
= NULL
;
2039 cur_track_info
= cmus_get_track_info(player_info
.filename
);
2041 player_info
.file_changed
= 0;
2042 needs_title_update
= 1;
2043 needs_status_update
= 1;
2045 if (player_info
.metadata_changed
) {
2046 player_info
.metadata_changed
= 0;
2047 needs_title_update
= 1;
2049 if (playlist
.status_changed
|| player_info
.position_changed
|| player_info
.status_changed
|| player_info
.volume_changed
) {
2050 player_info
.position_changed
= 0;
2051 player_info
.status_changed
= 0;
2052 player_info
.volume_changed
= 0;
2054 needs_status_update
= 1;
2056 switch (ui_curses_view
) {
2058 needs_view_update
+= playlist
.tree_win_changed
|| playlist
.track_win_changed
;
2061 needs_view_update
+= playlist
.shuffle_win_changed
;
2064 needs_view_update
+= playlist
.sorted_win_changed
;
2066 case PLAY_QUEUE_VIEW
:
2067 needs_view_update
+= play_queue_changed
;
2070 needs_view_update
+= browser_changed
;
2073 needs_view_update
+= filters_changed
;
2077 player_info_unlock();
2080 spawn_status_program();
2082 if (needs_view_update
|| needs_title_update
|| needs_status_update
|| needs_command_update
) {
2085 if (needs_view_update
)
2087 if (needs_title_update
)
2089 if (needs_status_update
) {
2090 /* don't lock pl before this */
2091 update_statusline();
2093 if (needs_command_update
)
2094 update_commandline();
2100 FD_SET(remote_socket
, &set
);
2101 #if defined(CONFIG_IRMAN)
2102 FD_SET(irman_fd
, &set
);
2106 rc
= select(fd_high
+ 1, &set
, NULL
, NULL
, &tv
);
2108 if (FD_ISSET(remote_socket
, &set
)) {
2109 remote_server_serve();
2111 if (FD_ISSET(0, &set
)) {
2116 rc
= u_getch(&ch
, &key
);
2119 if (key
== ERR
|| key
== 0) {
2131 if (ui_curses_input_mode
== NORMAL_MODE
) {
2133 } else if (ui_curses_input_mode
== COMMAND_MODE
) {
2134 command_mode_ch(ch
);
2135 ui_curses_update_commandline();
2136 } else if (ui_curses_input_mode
== SEARCH_MODE
) {
2138 ui_curses_update_commandline();
2140 } else if (rc
== 1) {
2142 if (ui_curses_input_mode
== NORMAL_MODE
) {
2143 normal_mode_key(key
);
2144 } else if (ui_curses_input_mode
== COMMAND_MODE
) {
2145 command_mode_key(key
);
2146 ui_curses_update_commandline();
2147 } else if (ui_curses_input_mode
== SEARCH_MODE
) {
2148 search_mode_key(key
);
2149 ui_curses_update_commandline();
2153 #if defined(CONFIG_IRMAN)
2154 if (FD_ISSET(irman_fd
, &set
)) {
2163 static int get_next(char **filename
)
2165 struct track_info
*info
;
2167 // FIXME: move player to cmus.c
2168 info
= play_queue_remove();
2170 info
= pl_set_next();
2175 *filename
= xstrdup(info
->filename
);
2176 track_info_unref(info
);
2180 static const struct player_callbacks player_callbacks
= {
2181 .get_next
= get_next
2184 static int ui_curses_init(void)
2189 term
= getenv("TERM");
2190 if (term
&& (strncmp(term
, "xterm", 5) == 0 || strncmp(term
, "rxvt", 4) == 0 || strcmp(term
, "screen") == 0))
2191 update_window_title
= 1;
2193 remote_socket
= remote_server_init(server_address
);
2194 if (remote_socket
< 0)
2197 rc
= player_init(&player_callbacks
);
2199 fprintf(stderr
, "%s: could not init player\n",
2201 remote_server_exit();
2208 remote_server_exit();
2211 searchable
= tree_searchable
;
2215 #if defined(CONFIG_IRMAN)
2220 remote_server_exit();
2227 if (sconf_get_bool_option(&sconf_head
, "continue", &btmp
) == 0)
2228 player_set_cont(btmp
);
2229 if (sconf_get_bool_option(&sconf_head
, "repeat", &btmp
) == 0)
2230 pl_set_repeat(btmp
);
2231 if (sconf_get_int_option(&sconf_head
, "playlist_mode", &btmp
) == 0) {
2232 if (btmp
< 0 || btmp
> 2)
2234 pl_set_playlist_mode(btmp
);
2236 if (sconf_get_int_option(&sconf_head
, "play_mode", &btmp
) == 0) {
2237 if (btmp
< 0 || btmp
> 2)
2239 pl_set_play_mode(btmp
);
2241 sconf_get_bool_option(&sconf_head
, "show_remaining_time", &show_remaining_time
);
2242 sconf_get_str_option(&sconf_head
, "status_display_program", &status_display_program
);
2243 ui_curses_set_sort("artist,album,discnumber,tracknumber,title,filename", 0);
2244 if (sconf_get_str_option(&sconf_head
, "sort", &sort
) == 0) {
2245 ui_curses_set_sort(sort
, 0);
2248 if (sconf_get_int_option(&sconf_head
, "buffer_chunks", &btmp
) == 0) {
2253 player_set_buffer_chunks(btmp
);
2260 /* commands_init must be after player_init */
2265 playlist_autosave_filename
= xstrjoin(cmus_config_dir
, "/playlist.pl");
2266 player_get_volume(&player_info
.vol_left
, &player_info
.vol_right
);
2270 static void ui_curses_exit(void)
2272 int repeat
, total_time
, buffer_chunks
;
2273 enum playlist_mode playlist_mode
;
2274 enum play_mode play_mode
;
2276 #if defined(CONFIG_IRMAN)
2279 remote_server_exit();
2282 cmus_save_playlist(playlist_autosave_filename
);
2284 pl_get_status(&repeat
, &playlist_mode
, &play_mode
, &total_time
);
2285 buffer_chunks
= player_get_buffer_chunks();
2291 track_info_unref(cur_track_info
);
2293 sconf_set_bool_option(&sconf_head
, "continue", player_info
.cont
);
2294 sconf_set_bool_option(&sconf_head
, "repeat", repeat
);
2295 sconf_set_int_option(&sconf_head
, "playlist_mode", playlist_mode
);
2296 sconf_set_int_option(&sconf_head
, "play_mode", play_mode
);
2297 sconf_set_bool_option(&sconf_head
, "show_remaining_time", show_remaining_time
);
2298 sconf_set_str_option(&sconf_head
, "status_display_program",
2299 status_display_program
? status_display_program
: "");
2300 sconf_set_str_option(&sconf_head
, "sort", sort_string
);
2301 sconf_set_int_option(&sconf_head
, "buffer_chunks", buffer_chunks
);
2304 free(playlist_autosave_filename
);
2305 free(status_display_program
);
2316 static void load_config(void)
2320 config_filename
= xstrjoin(cmus_config_dir
, "/config");
2321 rc
= sconf_load(&sconf_head
, config_filename
, &line
);
2322 if (rc
== -SCONF_ERROR_ERRNO
&& errno
!= ENOENT
) {
2323 fprintf(stderr
, "%s: error loading config file `%s': %s\n",
2328 } else if (rc
== -SCONF_ERROR_SYNTAX
) {
2329 fprintf(stderr
, "%s: syntax error in file `%s' on line %d\n",
2331 config_filename
, line
);
2337 #if defined(CONFIG_IRMAN)
2347 static struct option options
[NR_FLAGS
+ 1] = {
2348 #if defined(CONFIG_IRMAN)
2349 { 0, "irman-config", 0 },
2352 { 0, "plugins", 0 },
2354 { 0, "version", 0 },
2358 static const char *usage
=
2359 "Usage: %s [OPTION]...\n"
2360 "Curses based music player.\n"
2362 #if defined(CONFIG_IRMAN)
2363 " --irman-config configure irman settings\n"
2365 " --listen ADDR listen ADDR (unix socket) instead of /tmp/cmus-$USER\n"
2366 " --plugins list available plugins and exit\n"
2367 " --help display this help and exit\n"
2368 " --version " VERSION
"\n"
2370 "Use cmus-remote to control cmus from command line.\n"
2371 "Documentation: " DATADIR
"/cmus/doc/cmus.html\n"
2372 "Report bugs to <" PACKAGE_BUGREPORT
">.\n";
2374 int main(int argc
, char *argv
[])
2376 int configure_irman
= 0;
2377 int list_plugins
= 0;
2379 program_name
= argv
[0];
2385 rc
= get_option(&argv
, options
, 1, &idx
, &arg
);
2391 #if defined(CONFIG_IRMAN)
2392 case FLAG_IRMAN_CONFIG
:
2393 configure_irman
= 1;
2397 printf(usage
, program_name
);
2400 printf(PACKAGE
" " VERSION
"\nCopyright 2004-2005 Timo Hirvonen\n");
2406 server_address
= xstrdup(arg
);
2411 setlocale(LC_CTYPE
, "");
2412 charset
= nl_langinfo(CODESET
);
2413 if (strcmp(charset
, "UTF-8") == 0) {
2419 if (server_address
== NULL
) {
2420 server_address
= xnew(char, 256);
2421 snprintf(server_address
, 256, "/tmp/cmus-%s", user_name
);
2424 const char *debug_filename
= "/tmp/cmus-debug";
2425 FILE *f
= fopen(debug_filename
, "w");
2428 fprintf(stderr
, "%s: error opening `%s' for writing: %s\n",
2429 program_name
, debug_filename
, strerror(errno
));
2432 /* lots of debugging messages printed.
2433 * use log file. bugs are printed to stderr also.
2438 * DEBUG == 0: all debugging disabled
2439 * DEBUG == 1: only bugs are printed. use stderr instead of log file
2445 d_print("charset = '%s'\n", charset
);
2447 if (configure_irman
) {
2448 #if defined(CONFIG_IRMAN)
2453 player_init_plugins();
2455 player_dump_plugins();
2458 if (ui_curses_init())
2464 if (sconf_save(&sconf_head
, config_filename
)) {
2465 fprintf(stderr
, "%s: error saving `%s': %s\n", program_name
,
2466 config_filename
, strerror(errno
));
2468 sconf_free(&sconf_head
);
2469 free(config_filename
);