Customizable keys
[cmus.git] / cmus / ui_curses.c
blob6bde27bd799a13faeadda18ad756ff222992fb42
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
3 *
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
17 * 02111-1307, USA.
20 #include <ui_curses.h>
21 #include <cmdline.h>
22 #include <search_mode.h>
23 #include <command_mode.h>
24 #include <options.h>
25 #include <play_queue.h>
26 #include <browser.h>
27 #include <filters.h>
28 #include <cmus.h>
29 #include <player.h>
30 #include <utils.h>
31 #include <pl.h>
32 #include <xmalloc.h>
33 #include <xstrjoin.h>
34 #include <window.h>
35 #include <format_print.h>
36 #include <sconf.h>
37 #include <misc.h>
38 #include <uchar.h>
39 #include <spawn.h>
40 #include <file.h>
41 #include <get_option.h>
42 #include <server.h>
43 #include <path.h>
44 #include <config.h>
45 #include <keys.h>
47 #if defined(CONFIG_IRMAN)
48 #include <irman.h>
49 #include <irman_config.h>
50 #endif
52 #include <debug.h>
54 #include <unistd.h>
55 #include <stdlib.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <sys/ioctl.h>
59 #include <curses.h>
60 #include <signal.h>
61 #include <ctype.h>
62 #include <dirent.h>
63 #include <locale.h>
64 #include <langinfo.h>
65 #include <iconv.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] = {
92 -1,
93 -1,
94 COLOR_WHITE,
95 COLOR_WHITE,
96 -1,
97 -1,
98 COLOR_BLUE,
99 COLOR_BLUE,
102 COLOR_RED,
104 COLOR_WHITE,
105 COLOR_RED,
112 int fg_colors[NR_COLORS] = {
114 COLOR_YELLOW | BRIGHT,
115 COLOR_BLACK,
116 COLOR_YELLOW | BRIGHT,
118 COLOR_YELLOW | BRIGHT,
119 COLOR_WHITE | BRIGHT,
120 COLOR_YELLOW | BRIGHT,
122 COLOR_RED,
123 COLOR_WHITE | BRIGHT,
125 COLOR_BLACK,
126 COLOR_WHITE | BRIGHT,
127 COLOR_BLUE | BRIGHT,
129 COLOR_RED | BRIGHT,
130 COLOR_YELLOW | BRIGHT
133 /* prefixes actually. "_bg" or "_fg" added at end */
134 const char * const color_names[NR_COLORS] = {
135 "row",
136 "row_cur",
137 "row_sel",
138 "row_sel_cur",
139 "row_active",
140 "row_active_cur",
141 "row_active_sel",
142 "row_active_sel_cur",
143 "separator",
144 "title",
145 "commandline",
146 "statusline",
147 "titleline",
148 "browser_dir",
149 "browser_file",
150 "error",
151 "info",
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];
195 * printing
198 static void utf8_encode(const char *buffer)
200 static iconv_t cd = (iconv_t)-1;
201 size_t is, os;
202 const char *i;
203 char *o;
204 int rc;
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));
211 return;
214 i = buffer;
215 o = conv_buffer;
216 is = strlen(i);
217 os = sizeof(conv_buffer) - 1;
218 rc = iconv(cd, (void *)&i, &is, &o, &os);
219 *o = 0;
220 if (rc == -1) {
221 d_print("iconv failed: %s\n", strerror(errno));
222 return;
226 static void utf8_decode(const char *buffer)
228 static iconv_t cd = (iconv_t)-1;
229 size_t is, os;
230 const char *i;
231 char *o;
232 int rc;
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));
239 return;
242 i = buffer;
243 o = conv_buffer;
244 is = strlen(i);
245 os = sizeof(conv_buffer) - 1;
246 rc = iconv(cd, (void *)&i, &is, &o, &os);
247 *o = 0;
248 if (rc == -1) {
249 d_print("iconv failed: %s\n", strerror(errno));
250 return;
254 /* screen updates {{{ */
256 static void dump_print_buffer(int row, int col)
258 if (using_utf8) {
259 mvaddstr(row, col, print_buffer);
260 } else {
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)
268 int d, w;
270 w = u_str_width(str);
271 width -= 2 + indent;
272 indent++;
274 memset(print_buffer, ' ', indent);
275 d = indent;
276 if (w > width) {
277 width -= 3;
278 d += u_copy_chars(print_buffer + d, str, &width);
279 print_buffer[d++] = '.';
280 print_buffer[d++] = '.';
281 print_buffer[d++] = '.';
282 } else {
283 int s = 0;
285 while (str[s])
286 print_buffer[d++] = str[s++];
288 memset(print_buffer + d, ' ', width - w);
289 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)
298 int l;
300 l = strlen(str);
301 len -= 2;
303 print_buffer[0] = ' ';
304 if (l > len) {
305 memcpy(print_buffer + 1, str, len - 3);
306 print_buffer[len - 2] = '.';
307 print_buffer[len - 1] = '.';
308 print_buffer[len - 0] = '.';
309 } else {
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;
322 struct album *album;
323 struct iter sel;
324 int current, selected, active;
326 artist = iter_to_artist(iter);
327 album = iter_to_album(iter);
328 if (album) {
329 current = playlist.cur_album == album;
330 } else {
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]);
337 if (album) {
338 sprint(tree_win_y + row + 1, tree_win_x, album->name ? : noname, tree_win_w, 2);
339 } else {
340 sprint(tree_win_y + row + 1, tree_win_x, artist->name ? : noname, tree_win_w, 0);
344 enum {
345 TF_ARTIST,
346 TF_ALBUM,
347 TF_DISC,
348 TF_TRACK,
349 TF_TITLE,
350 TF_YEAR,
351 TF_GENRE,
352 TF_DURATION,
353 TF_PATHFILE,
354 TF_FILE,
355 NR_TFS
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' },
370 { { 0 }, 0, 0, 0 }
373 static inline void fopt_set_str(struct format_option *fopt, const char *str)
375 BUG_ON(fopt->type != FO_STR);
376 if (str) {
377 fopt->u.fo_str = str;
378 fopt->empty = 0;
379 } else {
380 fopt->empty = 1;
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;
388 fopt->empty = empty;
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;
395 fopt->empty = empty;
398 static void fill_track_fopts(struct track *track)
400 char *filename;
402 if (using_utf8) {
403 filename = track->info->filename;
404 } else {
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);
417 if (track->url) {
418 fopt_set_str(&track_fopts[TF_FILE], filename);
419 } else {
420 const char *f;
422 f = strrchr(filename, '/');
423 if (f) {
424 fopt_set_str(&track_fopts[TF_FILE], f + 1);
425 } else {
426 fopt_set_str(&track_fopts[TF_FILE], filename);
431 static void fill_track_fopts_track_info(struct track_info *info)
433 char *filename;
434 int num, disc;
436 if (using_utf8) {
437 filename = info->filename;
438 } else {
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);
456 } else {
457 const char *f;
459 f = strrchr(filename, '/');
460 if (f) {
461 fopt_set_str(&track_fopts[TF_FILE], f + 1);
462 } else {
463 fopt_set_str(&track_fopts[TF_FILE], filename);
468 static void print_track(struct window *win, int row, struct iter *iter)
470 struct track *track;
471 struct iter sel;
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);
483 } else {
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)
491 struct track *track;
492 struct iter sel;
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);
504 } else {
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)
512 struct track *track;
513 struct iter sel;
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);
525 } else {
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;
535 struct iter sel;
536 int current, selected, active;
538 e = iter_to_play_queue_entry(iter);
539 info = e->track_info;
541 window_get_sel(win, &sel);
542 current = 0;
543 selected = iters_equal(iter, &sel);
544 active = 1;
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);
551 } else {
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;
560 struct iter sel;
561 int selected;
563 e = iter_to_browser_entry(iter);
564 window_get_sel(win, &sel);
565 selected = iters_equal(iter, &sel);
566 if (selected) {
567 int active = 1;
568 int current = 0;
570 bkgdset(cursed_colors[(active << 2) | (selected << 1) | current]);
571 } else {
572 if (e->type == BROWSER_ENTRY_DIR) {
573 bkgdset(cursed_colors[COLOR_BROWSER_DIR]);
574 } else {
575 bkgdset(cursed_colors[COLOR_BROWSER_FILE]);
579 /* file name encoding == terminal encoding. no need to convert */
580 if (using_utf8) {
581 sprint(row + 1, 0, e->name, COLS, 0);
582 } else {
583 sprint_ascii(row + 1, 0, e->name, COLS);
587 static void print_filter(struct window *win, int row, struct iter *iter)
589 char buf[256];
590 struct filter_entry *e = iter_to_filter_entry(iter);
591 struct iter sel;
592 /* window active? */
593 int active = 1;
594 /* row selected? */
595 int selected;
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 *))
610 struct iter iter;
611 int nr_rows;
612 int c, i;
614 bkgdset(cursed_colors[COLOR_TITLE]);
615 c = snprintf(print_buffer, w + 1, " %s", title);
616 if (c > w)
617 c = w;
618 memset(print_buffer + c, ' ', w - c + 1);
619 print_buffer[w] = 0;
620 dump_print_buffer(y, x);
621 nr_rows = window_get_nr_rows(win);
622 i = 0;
623 if (window_get_top(win, &iter)) {
624 while (i < nr_rows) {
625 print(win, i, &iter);
626 i++;
627 if (!window_get_next(win, &iter))
628 break;
632 bkgdset(cursed_colors[0]);
633 memset(print_buffer, ' ', w);
634 print_buffer[w] = 0;
635 while (i < nr_rows) {
636 dump_print_buffer(y + i + 1, x);
637 i++;
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)
650 char title[512];
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)
660 char title[512];
661 char *filename;
663 filename = playlist_filename ? playlist_filename : playlist_autosave_filename;
664 if (using_utf8) {
665 /* already UTF-8 */
666 } else {
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)
677 char title[512];
678 char *filename;
680 filename = playlist_filename ? playlist_filename : playlist_autosave_filename;
681 if (using_utf8) {
682 /* already UTF-8 */
683 } else {
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)
700 char title[512];
701 char *dirname;
703 browser_changed = 0;
704 if (using_utf8) {
705 /* already UTF-8 */
706 dirname = browser_dir;
707 } else {
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)
717 filters_changed = 0;
718 update_window(filters_win, 0, 0, COLS, "Filters", print_filter);
721 static void draw_separator(void)
723 int row;
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) {
735 case TREE_VIEW:
736 pl_lock();
737 update_tree_window();
738 update_track_window();
739 pl_unlock();
740 draw_separator();
741 break;
742 case SHUFFLE_VIEW:
743 pl_lock();
744 update_shuffle_window();
745 pl_unlock();
746 break;
747 case SORTED_VIEW:
748 pl_lock();
749 update_sorted_window();
750 pl_unlock();
751 break;
752 case PLAY_QUEUE_VIEW:
753 play_queue_lock();
754 update_play_queue_window();
755 play_queue_unlock();
756 break;
757 case BROWSER_VIEW:
758 update_browser_window();
759 break;
760 case FILTERS_VIEW:
761 update_filters_window();
762 break;
766 enum {
767 SF_STATUS,
768 SF_POSITION,
769 SF_DURATION,
770 SF_TOTAL,
771 SF_VOLUME,
772 SF_LVOLUME,
773 SF_RVOLUME,
774 SF_BUFFER,
775 SF_REPEAT,
776 SF_CONTINUE,
777 SF_PLAYMODE,
778 SF_PLAYLISTMODE,
779 NR_SFS
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' },
797 { { 0 }, 0, 0, 0 }
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;
813 int duration = -1;
814 char *msg;
815 char format[80];
817 pl_get_status(&repeat, &playlist_mode, &play_mode, &total_time);
818 if (cur_track_info)
819 duration = cur_track_info->duration;
821 player_info_lock();
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);
830 } else {
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 ");
846 if (duration != -1)
847 strcat(format, "/ %d ");
848 if (player_info.vol_left != player_info.vol_right) {
849 strcat(format, "- %t vol: %l,%r ");
850 } else {
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);
866 if (msg) {
867 ui_curses_display_error_msg("%s", msg);
868 free(msg);
872 static void dump_buffer(const char *buffer)
874 if (using_utf8) {
875 addstr(buffer);
876 } else {
877 utf8_decode(buffer);
878 addstr(conv_buffer);
882 static void update_commandline(void)
884 int w;
885 char ch;
887 move(LINES - 1, 0);
888 if (error_msg[0]) {
889 bkgdset(cursed_colors[COLOR_ERROR]);
890 addstr(error_msg);
891 clrtoeol();
892 return;
894 bkgdset(cursed_colors[COLOR_COMMANDLINE]);
895 if (ui_curses_input_mode == NORMAL_MODE) {
896 clrtoeol();
897 return;
900 /* COMMAND_MODE or SEARCH_MODE */
901 w = u_str_width(cmdline.line);
902 ch = ':';
903 if (ui_curses_input_mode == SEARCH_MODE)
904 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
906 if (w <= COLS - 2) {
907 addch(ch);
908 dump_buffer(cmdline.line);
909 clrtoeol();
910 } else {
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;
918 if (skip > 0) {
919 /* skip the ':' */
920 skip--;
922 /* skip rest (if any) */
923 idx = u_skip_chars(cmdline.line, &skip);
925 width = COLS;
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
930 * was double-width)
932 print_buffer[idx++] = ' ';
933 width++;
935 print_buffer[idx] = 0;
936 dump_buffer(print_buffer);
937 } else {
938 /* print ':' + COLS - 1 chars */
939 addch(ch);
940 width = COLS - 1;
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];
952 char *ptr, *title;
954 ptr = strstr(player_info.metadata, "StreamTitle='");
955 if (ptr == NULL)
956 return NULL;
957 ptr += 13;
958 title = ptr;
959 while (*ptr) {
960 if (*ptr == '\'' && *(ptr + 1) == ';') {
961 memcpy(stream_title, title, ptr - title);
962 stream_title[ptr - title] = 0;
963 return stream_title;
965 ptr++;
967 return NULL;
970 static void update_titleline(void)
972 bkgdset(cursed_colors[COLOR_TITLELINE]);
973 player_info_lock();
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);
982 if (title == NULL)
983 use_alt_format = 1;
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);
990 } else {
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);
1006 } else {
1007 filename = strrchr(cur_track_info->filename, '/');
1008 if (filename) {
1009 fopt_set_str(&track_fopts[TF_FILE], filename + 1);
1010 } else {
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);
1016 } else {
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) {
1022 char *wtitle;
1023 int i;
1025 if (use_alt_format) {
1026 format_print(print_buffer, print_buffer_size, sizeof(print_buffer) - 1, window_title_alt_format, track_fopts);
1027 } else {
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] == ' ')
1034 i--;
1035 print_buffer[i + 1] = 0;
1037 if (using_utf8) {
1038 wtitle = print_buffer;
1039 } else {
1040 utf8_decode(print_buffer);
1041 wtitle = conv_buffer;
1044 printf("\033]0;%s\007", wtitle);
1045 fflush(stdout);
1047 } else {
1048 move(LINES - 3, 0);
1049 clrtoeol();
1051 if (update_window_title) {
1052 printf("\033]0;CMus " VERSION "\007");
1053 fflush(stdout);
1056 player_info_unlock();
1059 static int cmdline_cursor_column(void)
1061 int cw, skip, s;
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 */
1068 return 1 + cw;
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;
1078 /* skip the ':' */
1079 skip--;
1081 /* skip rest */
1082 s = skip;
1083 u_skip_chars(cmdline.line, &s);
1084 if (s > skip) {
1085 /* the last skipped char was double-width */
1086 return COLS - 2;
1088 return COLS - 1;
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());
1096 refresh();
1097 curs_set(1);
1098 } else {
1099 move(LINES - 1, 0);
1100 refresh();
1101 curs_set(0);
1105 void ui_curses_update_titleline(void)
1107 curs_set(0);
1108 update_titleline();
1109 post_update();
1112 static void ui_curses_update_commandline(void)
1114 curs_set(0);
1115 update_commandline();
1116 post_update();
1119 static void ui_curses_update_statusline(void)
1121 curs_set(0);
1122 update_statusline();
1123 post_update();
1126 void ui_curses_update_view(void)
1128 curs_set(0);
1129 update_view();
1130 post_update();
1133 void ui_curses_display_info_msg(const char *format, ...)
1135 va_list ap;
1137 va_start(ap, format);
1138 vsnprintf(error_msg, sizeof(error_msg), format, ap);
1139 va_end(ap);
1141 ui_curses_update_commandline();
1144 void ui_curses_display_error_msg(const char *format, ...)
1146 va_list ap;
1148 strcpy(error_msg, "Error: ");
1149 va_start(ap, format);
1150 vsnprintf(error_msg + 7, sizeof(error_msg) - 7, format, ap);
1151 va_end(ap);
1153 error_time = time(NULL);
1154 ui_curses_update_commandline();
1157 int ui_curses_yes_no_query(const char *format, ...)
1159 char buffer[512];
1160 va_list ap;
1161 int ret = 0;
1163 va_start(ap, format);
1164 vsnprintf(buffer, sizeof(buffer), format, ap);
1165 va_end(ap);
1167 move(LINES - 1, 0);
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).
1175 addstr(buffer);
1176 clrtoeol();
1177 refresh();
1179 while (1) {
1180 int ch = getch();
1182 if (ch == ERR || ch == 0)
1183 continue;
1184 if (ch == 'y')
1185 ret = 1;
1186 break;
1188 ui_curses_update_commandline();
1189 return ret;
1192 void ui_curses_search_not_found(void)
1194 const char *what = "Track";
1196 if (search_restricted) {
1197 switch (ui_curses_view) {
1198 case TREE_VIEW:
1199 what = "Artist/album";
1200 break;
1201 case SHUFFLE_VIEW:
1202 case SORTED_VIEW:
1203 case PLAY_QUEUE_VIEW:
1204 what = "Title";
1205 break;
1206 case BROWSER_VIEW:
1207 what = "File/Directory";
1208 break;
1209 case FILTERS_VIEW:
1210 what = "Filter";
1211 break;
1213 } else {
1214 switch (ui_curses_view) {
1215 case TREE_VIEW:
1216 case SHUFFLE_VIEW:
1217 case SORTED_VIEW:
1218 case PLAY_QUEUE_VIEW:
1219 what = "Track";
1220 break;
1221 case BROWSER_VIEW:
1222 what = "File/Directory";
1223 break;
1224 case FILTERS_VIEW:
1225 what = "Filter";
1226 break;
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)
1235 return;
1236 ui_curses_view = view;
1238 pl_lock();
1239 if (view < 3)
1240 __pl_set_view(view);
1241 switch (ui_curses_view) {
1242 case TREE_VIEW:
1243 searchable = tree_searchable;
1244 update_tree_window();
1245 update_track_window();
1246 draw_separator();
1247 break;
1248 case SHUFFLE_VIEW:
1249 searchable = shuffle_searchable;
1250 update_shuffle_window();
1251 break;
1252 case SORTED_VIEW:
1253 searchable = sorted_searchable;
1254 update_sorted_window();
1255 break;
1256 case PLAY_QUEUE_VIEW:
1257 searchable = play_queue_searchable;
1258 update_play_queue_window();
1259 break;
1260 case BROWSER_VIEW:
1261 searchable = browser_searchable;
1262 update_browser_window();
1263 break;
1264 case FILTERS_VIEW:
1265 searchable = filters_searchable;
1266 update_filters_window();
1267 break;
1269 pl_unlock();
1271 refresh();
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)
1312 error_msg[0] = 0;
1313 error_time = 0;
1314 ui_curses_input_mode = COMMAND_MODE;
1315 ui_curses_update_commandline();
1318 void ui_curses_search_mode(void)
1320 error_msg[0] = 0;
1321 error_time = 0;
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)
1329 error_msg[0] = 0;
1330 error_time = 0;
1331 ui_curses_input_mode = SEARCH_MODE;
1332 search_direction = SEARCH_BACKWARD;
1333 ui_curses_update_commandline();
1336 void ui_curses_quit(void)
1338 running = 0;
1341 void ui_curses_update_color(int idx)
1343 /* first color pair is 1 */
1344 int pair = idx + 1;
1345 int fg, bg, cursed;
1347 fg = clamp(fg_colors[idx], -1, 255);
1348 bg = clamp(bg_colors[idx], -1, 255);
1349 if (fg == -1) {
1350 init_pair(pair, fg, bg);
1351 cursed = COLOR_PAIR(pair);
1352 } else {
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);
1357 } else {
1358 init_pair(pair, fg, bg);
1359 cursed = COLOR_PAIR(pair);
1362 cursed_colors[idx] = cursed;
1365 static void full_update(void)
1367 curs_set(0);
1368 update_view();
1369 update_titleline();
1370 update_statusline();
1371 update_commandline();
1372 post_update();
1375 void ui_curses_set_sort(const char *value, int warn)
1377 static const char *valid[] = {
1378 "artist",
1379 "album",
1380 "title",
1381 "tracknumber",
1382 "discnumber",
1383 "date",
1384 "genre",
1385 "filename",
1386 NULL
1388 char **keys;
1389 int i, j;
1391 keys = bsplit(value, strlen(value), ',', 0);
1392 if (keys[0] != NULL && keys[0][0] == 0 && keys[1] == NULL) {
1393 /* value == '' */
1394 free(keys[0]);
1395 keys[0] = NULL;
1397 for (i = 0; keys[i]; i++) {
1398 for (j = 0; valid[j]; j++) {
1399 if (strcmp(keys[i], valid[j]) == 0)
1400 break;
1402 if (valid[j] == NULL) {
1403 if (warn)
1404 ui_curses_display_error_msg("invalid sort key '%s'", keys[i]);
1405 free_str_array(keys);
1406 return;
1409 pl_set_sort_keys(keys);
1410 free(sort_string);
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)
1421 int row, col;
1423 row = 1;
1424 col = 2;
1425 mvwaddstr(w, row++, (HELP_W - 11) / 2, "Global Keys");
1426 mvwaddstr(w, row++, (HELP_W - 11) / 2, "~~~~~~~~~~~");
1427 row++;
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");
1443 row = 4;
1444 col = 40;
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)
1464 int row, col;
1466 row = 1;
1467 col = 2;
1468 mvwaddstr(w, row++, col, "Tree / Shuffle / Sorted View Keys");
1469 mvwaddstr(w, row++, col, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
1470 row++;
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");
1479 row++;
1480 mvwaddstr(w, row++, col, "Play Queue Keys");
1481 mvwaddstr(w, row++, col, "~~~~~~~~~~~~~~~");
1482 row++;
1483 mvwaddstr(w, row++, col, "D, del - remove selected track");
1485 row = 1;
1486 col = 40;
1487 mvwaddstr(w, row++, col, "Directory Browser Keys");
1488 mvwaddstr(w, row++, col, "~~~~~~~~~~~~~~~~~~~~~~");
1489 row++;
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");
1499 row++;
1500 mvwaddstr(w, row++, col, "Filter View Keys");
1501 mvwaddstr(w, row++, col, "~~~~~~~~~~~~~~~~");
1502 row++;
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)
1510 int row, col;
1512 row = 1;
1513 col = 2;
1514 mvwaddstr(w, row++, (HELP_W - 9) / 2, "Searching");
1515 mvwaddstr(w, row++, (HELP_W - 9) / 2, "~~~~~~~~~");
1516 row++;
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");
1525 row++;
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.");
1528 row++;
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)
1537 int row, col;
1539 row = 1;
1540 col = 2;
1541 mvwaddstr(w, row++, (HELP_W - 12) / 2, "Command Mode");
1542 mvwaddstr(w, row++, (HELP_W - 12) / 2, "~~~~~~~~~~~~");
1543 row++;
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");
1558 row++;
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)
1565 int row, col;
1567 row = 1;
1568 col = 2;
1569 mvwaddstr(w, row++, (HELP_W - 7) / 2, "Options");
1570 mvwaddstr(w, row++, (HELP_W - 7) / 2, "~~~~~~~");
1571 row++;
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");
1586 row++;
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;
1594 char underline[64];
1595 int title_len = strlen(title);
1596 int row, col, i;
1598 for (i = 0; i < title_len; i++)
1599 underline[i] = '~';
1600 underline[i] = 0;
1601 row = 1;
1602 col = 2;
1603 mvwaddstr(w, row++, (HELP_W - title_len) / 2, title);
1604 mvwaddstr(w, row++, (HELP_W - title_len) / 2, underline);
1605 row++;
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");
1608 row++;
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)
1615 int x, y, page;
1616 WINDOW *w;
1618 if (COLS < HELP_WIDTH || LINES < HELP_HEIGHT) {
1619 ui_curses_display_error_msg("window is too small to display help");
1620 return;
1623 y = (COLS - HELP_WIDTH) / 2;
1624 if (y < 0)
1625 y = 0;
1626 x = (LINES - HELP_HEIGHT) / 2;
1627 if (x < 0)
1628 x = 0;
1629 w = newwin(HELP_HEIGHT, HELP_WIDTH, x, y);
1631 page = 0;
1632 while (1) {
1633 box(w, 0, 0);
1634 switch (page) {
1635 case 0:
1636 display_global_help(w);
1637 break;
1638 case 1:
1639 display_misc_help(w);
1640 break;
1641 case 2:
1642 display_search_mode_help(w);
1643 break;
1644 case 3:
1645 display_command_mode_help(w);
1646 break;
1647 case 4:
1648 display_options_help(w);
1649 break;
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");
1654 break;
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");
1658 wrefresh(w);
1659 while (1) {
1660 int ch = getch();
1662 if (ch == ' ') {
1663 if (page == LAST_HELP_PAGE) {
1664 page = 0;
1665 } else {
1666 page++;
1668 break;
1670 if (ch == 127 || ch == KEY_BACKSPACE) {
1671 if (page == 0) {
1672 page = LAST_HELP_PAGE;
1673 } else {
1674 page--;
1676 break;
1678 if (ch == '\n') {
1679 page = LAST_HELP_PAGE + 1;
1680 break;
1683 if (page > LAST_HELP_PAGE)
1684 break;
1685 wclear(w);
1688 delwin(w);
1689 full_update();
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)
1698 return;
1700 if (error_msg[0]) {
1701 error_time = 0;
1702 error_msg[0] = 0;
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;
1713 char *argv[32];
1714 int i, status;
1716 if (status_display_program == NULL || status_display_program[0] == 0)
1717 return;
1719 player_info_lock();
1720 status = player_info.status;
1721 if (status == 1)
1722 stream_title = get_stream_title(player_info.metadata);
1723 player_info_unlock();
1725 i = 0;
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 };
1732 int j;
1734 if (is_url(cur_track_info->filename)) {
1735 argv[i++] = xstrdup("url");
1736 argv[i++] = xstrdup(cur_track_info->filename);
1737 if (stream_title) {
1738 argv[i++] = xstrdup("title");
1739 argv[i++] = xstrdup(stream_title);
1741 } else {
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];
1746 const char *val;
1748 val = comments_get_val(cur_track_info->comments, key);
1749 if (val) {
1750 argv[i++] = xstrdup(key);
1751 argv[i++] = xstrdup(val);
1756 argv[i++] = NULL;
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++)
1760 free(argv[i]);
1763 static void finish(int sig)
1765 running = 0;
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)
1777 struct winsize ws;
1779 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1780 return -1;
1781 *columns = ws.ws_col;
1782 *lines = ws.ws_row;
1783 return 0;
1786 static void resize_playlist(int w, int h)
1788 tree_win_w = w / 3;
1789 track_win_w = w - tree_win_w - 1;
1790 if (tree_win_w < 8)
1791 tree_win_w = 8;
1792 if (track_win_w < 8)
1793 track_win_w = 8;
1794 tree_win_x = 0;
1795 tree_win_y = 0;
1796 track_win_x = tree_win_w + 1;
1797 track_win_y = 0;
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)
1807 char buf[64];
1808 int i;
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)
1820 char buf[64];
1821 int i;
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]);
1831 /* irman {{{ */
1832 #if defined(CONFIG_IRMAN)
1834 static struct irman *irman = NULL;
1835 static char *irman_device = NULL;
1836 static int irman_fd = -1;
1838 static struct {
1839 void (*function)(void);
1840 const char *option;
1841 char *text;
1842 } ir_commands[] = {
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];
1862 int i, rc;
1864 rc = irman_get_code(irman, code);
1865 if (rc) {
1866 d_print("irman_get_code: error: %s\n", strerror(errno));
1867 return;
1869 irman_code_to_text(text, code);
1870 for (i = 0; ir_commands[i].function; i++) {
1871 if (ir_commands[i].text == NULL)
1872 continue;
1873 if (strcmp(ir_commands[i].text, text) == 0) {
1874 ir_commands[i].function();
1875 break;
1880 static int ir_init(void)
1882 int i;
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",
1889 program_name);
1890 return 1;
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,
1896 strerror(errno));
1897 return 1;
1899 irman_fd = irman_get_fd(irman);
1900 return 0;
1903 static void ir_exit(void)
1905 irman_close(irman);
1907 #endif
1909 /* irman }}} */
1911 static int u_getch(uchar *uch, int *keyp)
1913 int key;
1914 int bit = 7;
1915 uchar u, ch;
1916 int mask = (1 << 7);
1918 key = getch();
1919 if (key == ERR || key == 0)
1920 return -1;
1921 if (key > 255) {
1922 *keyp = key;
1923 return 1;
1925 ch = (unsigned char)key;
1926 while (bit > 0 && ch & mask) {
1927 mask >>= 1;
1928 bit--;
1930 if (bit == 7) {
1931 /* ascii */
1932 u = ch;
1933 } else {
1934 int count;
1936 u = ch & ((1 << bit) - 1);
1937 count = 6 - bit;
1938 while (count) {
1939 key = getch();
1940 if (key == ERR || key == 0)
1941 return -1;
1942 ch = (unsigned char)key;
1943 u = (u << 6) | (ch & 63);
1944 count--;
1947 *uch = u;
1948 return 0;
1951 static void ui_curses_start(void)
1953 struct sigaction act;
1954 int rc;
1955 int fd_high;
1957 fd_high = remote_socket;
1958 #if defined(CONFIG_IRMAN)
1959 if (irman_fd > fd_high)
1960 fd_high = irman_fd;
1961 #endif
1962 cmus_load_playlist(playlist_autosave_filename);
1964 signal(SIGINT, finish);
1966 sigemptyset(&act.sa_mask);
1967 act.sa_flags = 0;
1968 act.sa_handler = sig_winch;
1969 sigaction(SIGWINCH, &act, NULL);
1971 initscr();
1973 /* turn off kb buffering */
1974 cbreak();
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()
1981 halfdelay(5);
1983 noecho();
1984 if (has_colors()) {
1985 int i;
1987 start_color();
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);
1994 while (running) {
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;
2001 fd_set set;
2002 struct timeval tv;
2004 if (needs_to_resize) {
2005 int w, h;
2006 int columns, lines;
2008 if (get_window_size(&lines, &columns) == 0) {
2009 needs_to_resize = 0;
2010 resizeterm(lines, columns);
2011 w = COLS;
2012 h = LINES - 3;
2013 if (w < 16)
2014 w = 16;
2015 if (h < 8)
2016 h = 8;
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;
2028 player_info_lock();
2029 pl_lock();
2031 needs_spawn = player_info.status_changed || player_info.file_changed || player_info.metadata_changed;
2033 if (player_info.file_changed) {
2034 if (cur_track_info)
2035 track_info_unref(cur_track_info);
2036 if (player_info.filename[0] == 0) {
2037 cur_track_info = NULL;
2038 } else {
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) {
2057 case TREE_VIEW:
2058 needs_view_update += playlist.tree_win_changed || playlist.track_win_changed;
2059 break;
2060 case SHUFFLE_VIEW:
2061 needs_view_update += playlist.shuffle_win_changed;
2062 break;
2063 case SORTED_VIEW:
2064 needs_view_update += playlist.sorted_win_changed;
2065 break;
2066 case PLAY_QUEUE_VIEW:
2067 needs_view_update += play_queue_changed;
2068 break;
2069 case BROWSER_VIEW:
2070 needs_view_update += browser_changed;
2071 break;
2072 case FILTERS_VIEW:
2073 needs_view_update += filters_changed;
2074 break;
2076 pl_unlock();
2077 player_info_unlock();
2079 if (needs_spawn)
2080 spawn_status_program();
2082 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
2083 curs_set(0);
2085 if (needs_view_update)
2086 update_view();
2087 if (needs_title_update)
2088 update_titleline();
2089 if (needs_status_update) {
2090 /* don't lock pl before this */
2091 update_statusline();
2093 if (needs_command_update)
2094 update_commandline();
2095 post_update();
2098 FD_ZERO(&set);
2099 FD_SET(0, &set);
2100 FD_SET(remote_socket, &set);
2101 #if defined(CONFIG_IRMAN)
2102 FD_SET(irman_fd, &set);
2103 #endif
2104 tv.tv_sec = 0;
2105 tv.tv_usec = 50e3;
2106 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
2107 if (rc > 0) {
2108 if (FD_ISSET(remote_socket, &set)) {
2109 remote_server_serve();
2111 if (FD_ISSET(0, &set)) {
2112 int key = 0;
2113 uchar ch;
2115 if (using_utf8) {
2116 rc = u_getch(&ch, &key);
2117 } else {
2118 ch = key = getch();
2119 if (key == ERR || key == 0) {
2120 rc = -1;
2121 } else {
2122 if (key > 255) {
2123 rc = 1;
2124 } else {
2125 rc = 0;
2129 if (rc == 0) {
2130 clear_error();
2131 if (ui_curses_input_mode == NORMAL_MODE) {
2132 normal_mode_ch(ch);
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) {
2137 search_mode_ch(ch);
2138 ui_curses_update_commandline();
2140 } else if (rc == 1) {
2141 clear_error();
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)) {
2155 ir_read();
2157 #endif
2160 endwin();
2163 static int get_next(char **filename)
2165 struct track_info *info;
2167 // FIXME: move player to cmus.c
2168 info = play_queue_remove();
2169 if (info == NULL) {
2170 info = pl_set_next();
2171 if (info == NULL)
2172 return -1;
2175 *filename = xstrdup(info->filename);
2176 track_info_unref(info);
2177 return 0;
2180 static const struct player_callbacks player_callbacks = {
2181 .get_next = get_next
2184 static int ui_curses_init(void)
2186 int rc, btmp;
2187 char *term, *sort;
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)
2195 return 1;
2197 rc = player_init(&player_callbacks);
2198 if (rc) {
2199 fprintf(stderr, "%s: could not init player\n",
2200 program_name);
2201 remote_server_exit();
2202 return rc;
2205 rc = pl_init();
2206 if (rc) {
2207 player_exit();
2208 remote_server_exit();
2209 return rc;
2211 searchable = tree_searchable;
2213 cmus_init();
2215 #if defined(CONFIG_IRMAN)
2216 rc = ir_init();
2217 if (rc) {
2218 pl_exit();
2219 player_exit();
2220 remote_server_exit();
2221 return rc;
2223 #endif
2225 /* init curses */
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)
2233 btmp = 0;
2234 pl_set_playlist_mode(btmp);
2236 if (sconf_get_int_option(&sconf_head, "play_mode", &btmp) == 0) {
2237 if (btmp < 0 || btmp > 2)
2238 btmp = 0;
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);
2246 free(sort);
2248 if (sconf_get_int_option(&sconf_head, "buffer_chunks", &btmp) == 0) {
2249 if (btmp < 3)
2250 btmp = 3;
2251 if (btmp > 30)
2252 btmp = 30;
2253 player_set_buffer_chunks(btmp);
2255 get_colors();
2257 browser_init();
2258 filters_init();
2259 cmdline_init();
2260 /* commands_init must be after player_init */
2261 commands_init();
2262 options_init();
2263 search_mode_init();
2264 keys_init();
2265 playlist_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
2266 player_get_volume(&player_info.vol_left, &player_info.vol_right);
2267 return 0;
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)
2277 ir_exit();
2278 #endif
2279 remote_server_exit();
2281 cmus_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();
2287 player_exit();
2288 pl_exit();
2290 if (cur_track_info)
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);
2302 set_colors();
2304 free(playlist_autosave_filename);
2305 free(status_display_program);
2306 free(sort_string);
2308 keys_exit();
2309 options_exit();
2310 commands_exit();
2311 search_mode_exit();
2312 filters_exit();
2313 browser_exit();
2316 static void load_config(void)
2318 int rc, line;
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",
2324 program_name,
2325 config_filename,
2326 strerror(errno));
2327 exit(1);
2328 } else if (rc == -SCONF_ERROR_SYNTAX) {
2329 fprintf(stderr, "%s: syntax error in file `%s' on line %d\n",
2330 program_name,
2331 config_filename, line);
2332 exit(1);
2336 enum {
2337 #if defined(CONFIG_IRMAN)
2338 FLAG_IRMAN_CONFIG,
2339 #endif
2340 FLAG_LISTEN,
2341 FLAG_PLUGINS,
2342 FLAG_HELP,
2343 FLAG_VERSION,
2344 NR_FLAGS
2347 static struct option options[NR_FLAGS + 1] = {
2348 #if defined(CONFIG_IRMAN)
2349 { 0, "irman-config", 0 },
2350 #endif
2351 { 0, "listen", 1 },
2352 { 0, "plugins", 0 },
2353 { 0, "help", 0 },
2354 { 0, "version", 0 },
2355 { 0, NULL, 0 }
2358 static const char *usage =
2359 "Usage: %s [OPTION]...\n"
2360 "Curses based music player.\n"
2361 "\n"
2362 #if defined(CONFIG_IRMAN)
2363 " --irman-config configure irman settings\n"
2364 #endif
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"
2369 "\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];
2380 argv++;
2381 while (1) {
2382 int rc, idx;
2383 char *arg;
2385 rc = get_option(&argv, options, 1, &idx, &arg);
2386 if (rc == 1)
2387 break;
2388 if (rc > 1)
2389 return 1;
2390 switch (idx) {
2391 #if defined(CONFIG_IRMAN)
2392 case FLAG_IRMAN_CONFIG:
2393 configure_irman = 1;
2394 break;
2395 #endif
2396 case FLAG_HELP:
2397 printf(usage, program_name);
2398 return 0;
2399 case FLAG_VERSION:
2400 printf(PACKAGE " " VERSION "\nCopyright 2004-2005 Timo Hirvonen\n");
2401 return 0;
2402 case FLAG_PLUGINS:
2403 list_plugins = 1;
2404 break;
2405 case FLAG_LISTEN:
2406 server_address = xstrdup(arg);
2407 break;
2411 setlocale(LC_CTYPE, "");
2412 charset = nl_langinfo(CODESET);
2413 if (strcmp(charset, "UTF-8") == 0) {
2414 using_utf8 = 1;
2415 } else {
2416 using_utf8 = 0;
2418 misc_init();
2419 if (server_address == NULL) {
2420 server_address = xnew(char, 256);
2421 snprintf(server_address, 256, "/tmp/cmus-%s", user_name);
2423 if (DEBUG > 1) {
2424 const char *debug_filename = "/tmp/cmus-debug";
2425 FILE *f = fopen(debug_filename, "w");
2427 if (f == NULL) {
2428 fprintf(stderr, "%s: error opening `%s' for writing: %s\n",
2429 program_name, debug_filename, strerror(errno));
2430 return 1;
2432 /* lots of debugging messages printed.
2433 * use log file. bugs are printed to stderr also.
2435 debug_init(f);
2436 } else {
2438 * DEBUG == 0: all debugging disabled
2439 * DEBUG == 1: only bugs are printed. use stderr instead of log file
2441 debug_init(stderr);
2443 load_config();
2445 d_print("charset = '%s'\n", charset);
2447 if (configure_irman) {
2448 #if defined(CONFIG_IRMAN)
2449 if (irman_config())
2450 return 1;
2451 #endif
2452 } else {
2453 player_init_plugins();
2454 if (list_plugins) {
2455 player_dump_plugins();
2456 return 0;
2458 if (ui_curses_init())
2459 return 1;
2460 ui_curses_start();
2461 ui_curses_exit();
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);
2470 return 0;