AAC: Detect corrupted stream
[cmus.git] / ui_curses.c
blob9de63ccbb29664e3cc4660b7529aff6dd2295c16
1 /*
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
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 "lib.h"
32 #include "pl.h"
33 #include "xmalloc.h"
34 #include "xstrjoin.h"
35 #include "window.h"
36 #include "format_print.h"
37 #include "misc.h"
38 #include "prog.h"
39 #include "uchar.h"
40 #include "spawn.h"
41 #include "server.h"
42 #include "keys.h"
43 #include "debug.h"
44 #include "help.h"
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <errno.h>
50 #include <sys/ioctl.h>
51 #include <ctype.h>
52 #include <dirent.h>
53 #include <locale.h>
54 #include <langinfo.h>
55 #include <iconv.h>
56 #include <signal.h>
57 #include <stdarg.h>
59 #if defined(__sun__) || defined(__CYGWIN__)
60 /* TIOCGWINSZ */
61 #include <termios.h>
62 #include <ncurses.h>
63 #else
64 #include <curses.h>
65 #endif
67 /* defined in <term.h> but without const */
68 char *tgetstr(const char *id, char **area);
69 char *tgoto(const char *cap, int col, int row);
71 /* globals. documented in ui_curses.h */
73 int ui_initialized = 0;
74 enum ui_input_mode input_mode = NORMAL_MODE;
75 int cur_view = TREE_VIEW;
76 struct searchable *searchable;
78 /* display parse errors? (command line) */
79 int display_errors = 0;
81 char *lib_filename = NULL;
82 char *pl_filename = NULL;
84 /* ------------------------------------------------------------------------- */
86 static int running = 1;
87 static char *lib_autosave_filename;
88 static char *pl_autosave_filename;
90 /* shown error message and time stamp
91 * error is cleared if it is older than 3s and key was pressed
93 static char error_buf[512];
94 static time_t error_time = 0;
95 /* info messages are displayed in different color */
96 static int msg_is_error;
97 static int error_count = 0;
99 static char *server_address = NULL;
101 static char *charset = NULL;
102 static char print_buffer[512];
104 /* destination buffer for utf8_encode and utf8_decode */
105 static char conv_buffer[512];
107 #define print_buffer_size (sizeof(print_buffer) - 1)
108 static int using_utf8;
110 static const char *t_ts;
111 static const char *t_fs;
113 static int tree_win_x = 0;
114 static int tree_win_y = 0;
115 static int tree_win_w = 0;
117 static int track_win_x = 0;
118 static int track_win_y = 0;
119 static int track_win_w = 0;
121 static int cursor_x;
122 static int cursor_y;
124 enum {
125 CURSED_WIN,
126 CURSED_WIN_CUR,
127 CURSED_WIN_SEL,
128 CURSED_WIN_SEL_CUR,
130 CURSED_WIN_ACTIVE,
131 CURSED_WIN_ACTIVE_CUR,
132 CURSED_WIN_ACTIVE_SEL,
133 CURSED_WIN_ACTIVE_SEL_CUR,
135 CURSED_SEPARATOR,
136 CURSED_WIN_TITLE,
137 CURSED_COMMANDLINE,
138 CURSED_STATUSLINE,
140 CURSED_TITLELINE,
141 CURSED_DIR,
142 CURSED_ERROR,
143 CURSED_INFO,
145 NR_CURSED
148 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
149 COLOR_WIN_BG,
150 COLOR_WIN_BG,
151 COLOR_WIN_INACTIVE_SEL_BG,
152 COLOR_WIN_INACTIVE_CUR_SEL_BG,
154 COLOR_WIN_BG,
155 COLOR_WIN_BG,
156 COLOR_WIN_SEL_BG,
157 COLOR_WIN_CUR_SEL_BG,
159 COLOR_WIN_BG,
160 COLOR_WIN_TITLE_BG,
161 COLOR_CMDLINE_BG,
162 COLOR_STATUSLINE_BG,
164 COLOR_TITLELINE_BG,
165 COLOR_WIN_BG,
166 COLOR_CMDLINE_BG,
167 COLOR_CMDLINE_BG
170 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
171 COLOR_WIN_FG,
172 COLOR_WIN_CUR,
173 COLOR_WIN_INACTIVE_SEL_FG,
174 COLOR_WIN_INACTIVE_CUR_SEL_FG,
176 COLOR_WIN_FG,
177 COLOR_WIN_CUR,
178 COLOR_WIN_SEL_FG,
179 COLOR_WIN_CUR_SEL_FG,
181 COLOR_SEPARATOR,
182 COLOR_WIN_TITLE_FG,
183 COLOR_CMDLINE_FG,
184 COLOR_STATUSLINE_FG,
186 COLOR_TITLELINE_FG,
187 COLOR_WIN_DIR,
188 COLOR_ERROR,
189 COLOR_INFO
192 /* index is CURSED_*, value is fucking color pair */
193 static int pairs[NR_CURSED];
195 enum {
196 TF_ARTIST,
197 TF_ALBUM,
198 TF_DISC,
199 TF_TRACK,
200 TF_TITLE,
201 TF_YEAR,
202 TF_GENRE,
203 TF_DURATION,
204 TF_PATHFILE,
205 TF_FILE,
206 NR_TFS
209 static struct format_option track_fopts[NR_TFS + 1] = {
210 DEF_FO_STR('a'),
211 DEF_FO_STR('l'),
212 DEF_FO_INT('D'),
213 DEF_FO_INT('n'),
214 DEF_FO_STR('t'),
215 DEF_FO_STR('y'),
216 DEF_FO_STR('g'),
217 DEF_FO_TIME('d'),
218 DEF_FO_STR('f'),
219 DEF_FO_STR('F'),
220 DEF_FO_END
223 enum {
224 SF_STATUS,
225 SF_POSITION,
226 SF_DURATION,
227 SF_TOTAL,
228 SF_VOLUME,
229 SF_LVOLUME,
230 SF_RVOLUME,
231 SF_BUFFER,
232 SF_REPEAT,
233 SF_CONTINUE,
234 SF_SHUFFLE,
235 SF_PLAYLISTMODE,
236 NR_SFS
239 static struct format_option status_fopts[NR_SFS + 1] = {
240 DEF_FO_STR('s'),
241 DEF_FO_TIME('p'),
242 DEF_FO_TIME('d'),
243 DEF_FO_TIME('t'),
244 DEF_FO_INT('v'),
245 DEF_FO_INT('l'),
246 DEF_FO_INT('r'),
247 DEF_FO_INT('b'),
248 DEF_FO_STR('R'),
249 DEF_FO_STR('C'),
250 DEF_FO_STR('S'),
251 DEF_FO_STR('L'),
252 DEF_FO_END
255 static void utf8_encode(const char *buffer)
257 static iconv_t cd = (iconv_t)-1;
258 size_t is, os;
259 const char *i;
260 char *o;
261 int rc;
263 if (cd == (iconv_t)-1) {
264 d_print("iconv_open(UTF-8, %s)\n", charset);
265 cd = iconv_open("UTF-8", charset);
266 if (cd == (iconv_t)-1) {
267 d_print("iconv_open failed: %s\n", strerror(errno));
268 return;
271 i = buffer;
272 o = conv_buffer;
273 is = strlen(i);
274 os = sizeof(conv_buffer) - 1;
275 rc = iconv(cd, (void *)&i, &is, &o, &os);
276 *o = 0;
277 if (rc == -1) {
278 d_print("iconv failed: %s\n", strerror(errno));
279 return;
283 static void utf8_decode(const char *buffer)
285 static iconv_t cd = (iconv_t)-1;
286 size_t is, os;
287 const char *i;
288 char *o;
289 int rc;
291 if (cd == (iconv_t)-1) {
292 d_print("iconv_open(%s, UTF-8)\n", charset);
293 cd = iconv_open(charset, "UTF-8");
294 if (cd == (iconv_t)-1) {
295 d_print("iconv_open failed: %s\n", strerror(errno));
296 return;
299 i = buffer;
300 o = conv_buffer;
301 is = strlen(i);
302 os = sizeof(conv_buffer) - 1;
303 rc = iconv(cd, (void *)&i, &is, &o, &os);
304 *o = 0;
305 if (rc == -1) {
306 d_print("iconv failed: %s\n", strerror(errno));
307 return;
311 /* screen updates {{{ */
313 static void dump_print_buffer(int row, int col)
315 if (using_utf8) {
316 mvaddstr(row, col, print_buffer);
317 } else {
318 utf8_decode(print_buffer);
319 mvaddstr(row, col, conv_buffer);
323 /* print @str into @buf
325 * if @str is shorter than @width pad with spaces
326 * if @str is wider than @width truncate and add "..."
328 static int format_str(char *buf, const char *str, int width)
330 int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;
332 while (1) {
333 uchar u;
334 int w;
336 u_get_char(str, &s, &u);
337 if (u == 0) {
338 memset(buf + d, ' ', width);
339 d += width;
340 break;
343 w = u_char_width(u);
344 if (width == 3)
345 ellipsis_pos = d;
346 if (width == 4 && w == 2) {
347 /* can't cut double-width char */
348 ellipsis_pos = d + 1;
349 cut_double_width = 1;
352 width -= w;
353 if (width < 0) {
354 /* does not fit */
355 d = ellipsis_pos;
356 if (cut_double_width) {
357 /* first half of the double-width char */
358 buf[d - 1] = ' ';
360 buf[d++] = '.';
361 buf[d++] = '.';
362 buf[d++] = '.';
363 break;
365 u_set_char(buf, &d, u);
367 return d;
370 static void sprint(int row, int col, const char *str, int width)
372 int pos = 0;
374 print_buffer[pos++] = ' ';
375 pos += format_str(print_buffer + pos, str, width - 2);
376 print_buffer[pos++] = ' ';
377 print_buffer[pos] = 0;
378 dump_print_buffer(row, col);
381 static void sprint_ascii(int row, int col, const char *str, int len)
383 int l;
385 l = strlen(str);
386 len -= 2;
388 print_buffer[0] = ' ';
389 if (l > len) {
390 memcpy(print_buffer + 1, str, len - 3);
391 print_buffer[len - 2] = '.';
392 print_buffer[len - 1] = '.';
393 print_buffer[len - 0] = '.';
394 } else {
395 memcpy(print_buffer + 1, str, l);
396 memset(print_buffer + 1 + l, ' ', len - l);
398 print_buffer[len + 1] = ' ';
399 print_buffer[len + 2] = 0;
400 mvaddstr(row, col, print_buffer);
403 static void print_tree(struct window *win, int row, struct iter *iter)
405 const char *str;
406 struct artist *artist;
407 struct album *album;
408 struct iter sel;
409 int current, selected, active, pos;
411 artist = iter_to_artist(iter);
412 album = iter_to_album(iter);
413 current = 0;
414 if (lib_cur_track) {
415 if (album) {
416 current = CUR_ALBUM == album;
417 } else {
418 current = CUR_ARTIST == artist;
421 window_get_sel(win, &sel);
422 selected = iters_equal(iter, &sel);
423 active = lib_cur_win == lib_tree_win;
424 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
426 if (active && selected) {
427 cursor_x = 0;
428 cursor_y = 1 + row;
431 pos = 0;
432 print_buffer[pos++] = ' ';
433 str = artist->name;
434 if (album) {
435 print_buffer[pos++] = ' ';
436 print_buffer[pos++] = ' ';
437 str = album->name;
439 pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
440 print_buffer[pos++] = ' ';
441 print_buffer[pos++] = 0;
442 dump_print_buffer(tree_win_y + row + 1, tree_win_x);
445 static inline void fopt_set_str(struct format_option *fopt, const char *str)
447 BUG_ON(fopt->type != FO_STR);
448 if (str) {
449 fopt->fo_str = str;
450 fopt->empty = 0;
451 } else {
452 fopt->empty = 1;
456 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
458 BUG_ON(fopt->type != FO_INT);
459 fopt->fo_int = value;
460 fopt->empty = empty;
463 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
465 BUG_ON(fopt->type != FO_TIME);
466 fopt->fo_time = value;
467 fopt->empty = empty;
470 static void fill_track_fopts_track_info(struct track_info *info)
472 char *filename;
473 int num, disc;
475 if (using_utf8) {
476 filename = info->filename;
477 } else {
478 utf8_encode(info->filename);
479 filename = conv_buffer;
481 disc = comments_get_int(info->comments, "discnumber");
482 num = comments_get_int(info->comments, "tracknumber");
484 fopt_set_str(&track_fopts[TF_ARTIST], comments_get_val(info->comments, "artist"));
485 fopt_set_str(&track_fopts[TF_ALBUM], comments_get_val(info->comments, "album"));
486 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
487 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
488 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(info->comments, "title"));
489 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(info->comments, "date"));
490 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(info->comments, "genre"));
491 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
492 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
493 if (is_url(info->filename)) {
494 fopt_set_str(&track_fopts[TF_FILE], filename);
495 } else {
496 const char *f;
498 f = strrchr(filename, '/');
499 if (f) {
500 fopt_set_str(&track_fopts[TF_FILE], f + 1);
501 } else {
502 fopt_set_str(&track_fopts[TF_FILE], filename);
507 static void print_track(struct window *win, int row, struct iter *iter)
509 struct tree_track *track;
510 struct iter sel;
511 int current, selected, active;
513 track = iter_to_tree_track(iter);
514 current = lib_cur_track == track;
515 window_get_sel(win, &sel);
516 selected = iters_equal(iter, &sel);
517 active = lib_cur_win == lib_track_win;
518 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
520 if (active && selected) {
521 cursor_x = track_win_x;
522 cursor_y = 1 + row;
525 fill_track_fopts_track_info(tree_track_info(track));
527 if (track_info_has_tag(tree_track_info(track))) {
528 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
529 } else {
530 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
532 dump_print_buffer(track_win_y + row + 1, track_win_x);
535 /* used by print_editable only */
536 static struct simple_track *current_track;
538 static void print_editable(struct window *win, int row, struct iter *iter)
540 struct simple_track *track;
541 struct iter sel;
542 int current, selected, active;
544 track = iter_to_simple_track(iter);
545 current = current_track == track;
546 window_get_sel(win, &sel);
547 selected = iters_equal(iter, &sel);
549 if (selected) {
550 cursor_x = 0;
551 cursor_y = 1 + row;
554 active = 1;
555 if (!selected && track->marked) {
556 selected = 1;
557 active = 0;
560 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
562 fill_track_fopts_track_info(track->info);
564 if (track_info_has_tag(track->info)) {
565 format_print(print_buffer, COLS, list_win_format, track_fopts);
566 } else {
567 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
569 dump_print_buffer(row + 1, 0);
572 static void print_browser(struct window *win, int row, struct iter *iter)
574 struct browser_entry *e;
575 struct iter sel;
576 int selected;
578 e = iter_to_browser_entry(iter);
579 window_get_sel(win, &sel);
580 selected = iters_equal(iter, &sel);
581 if (selected) {
582 int active = 1;
583 int current = 0;
585 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
586 } else {
587 if (e->type == BROWSER_ENTRY_DIR) {
588 bkgdset(pairs[CURSED_DIR]);
589 } else {
590 bkgdset(pairs[CURSED_WIN]);
594 if (selected) {
595 cursor_x = 0;
596 cursor_y = 1 + row;
599 /* file name encoding == terminal encoding. no need to convert */
600 if (using_utf8) {
601 sprint(row + 1, 0, e->name, COLS);
602 } else {
603 sprint_ascii(row + 1, 0, e->name, COLS);
607 static void print_filter(struct window *win, int row, struct iter *iter)
609 char buf[256];
610 struct filter_entry *e = iter_to_filter_entry(iter);
611 struct iter sel;
612 /* window active? */
613 int active = 1;
614 /* row selected? */
615 int selected;
616 /* is the filter currently active? */
617 int current = !!e->act_stat;
618 const char stat_chars[3] = " *!";
619 int ch1, ch2, ch3, pos;
621 window_get_sel(win, &sel);
622 selected = iters_equal(iter, &sel);
623 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
625 if (selected) {
626 cursor_x = 0;
627 cursor_y = 1 + row;
630 ch1 = ' ';
631 ch3 = ' ';
632 if (e->sel_stat != e->act_stat) {
633 ch1 = '[';
634 ch3 = ']';
636 ch2 = stat_chars[e->sel_stat];
637 snprintf(buf, sizeof(buf), "%c%c%c%-15s %s", ch1, ch2, ch3, e->name, e->filter);
638 pos = format_str(print_buffer, buf, COLS - 1);
639 print_buffer[pos++] = ' ';
640 print_buffer[pos] = 0;
641 dump_print_buffer(row + 1, 0);
644 static void print_help(struct window *win, int row, struct iter *iter)
646 struct iter sel;
647 int selected;
648 int pos;
649 char buf[512];
650 const struct help_entry *e = iter_to_help_entry(iter);
651 const struct cmus_opt *opt;
653 window_get_sel(win, &sel);
654 selected = iters_equal(iter, &sel);
655 bkgdset(pairs[selected << 1]);
657 if (selected) {
658 cursor_x = 0;
659 cursor_y = 1 + row;
662 switch (e->type) {
663 case HE_TEXT:
664 snprintf(buf, sizeof(buf), " %s", e->text);
665 break;
666 case HE_BOUND:
667 snprintf(buf, sizeof(buf), " %-8s %-14s %s",
668 key_context_names[e->binding->ctx],
669 e->binding->key->name,
670 e->binding->cmd);
671 break;
672 case HE_UNBOUND:
673 snprintf(buf, sizeof(buf), " %s", e->command->name);
674 break;
675 case HE_OPTION:
676 opt = e->option;
677 snprintf(buf, sizeof(buf), " %-29s ", opt->name);
678 opt->get(opt->id, buf + strlen(buf));
679 break;
681 pos = format_str(print_buffer, buf, COLS - 1);
682 print_buffer[pos++] = ' ';
683 print_buffer[pos] = 0;
684 dump_print_buffer(row + 1, 0);
687 static void update_window(struct window *win, int x, int y, int w, const char *title,
688 void (*print)(struct window *, int, struct iter *))
690 struct iter iter;
691 int nr_rows;
692 int c, i;
694 win->changed = 0;
696 bkgdset(pairs[CURSED_WIN_TITLE]);
697 c = snprintf(print_buffer, w + 1, " %s", title);
698 if (c > w)
699 c = w;
700 memset(print_buffer + c, ' ', w - c + 1);
701 print_buffer[w] = 0;
702 dump_print_buffer(y, x);
703 nr_rows = window_get_nr_rows(win);
704 i = 0;
705 if (window_get_top(win, &iter)) {
706 while (i < nr_rows) {
707 print(win, i, &iter);
708 i++;
709 if (!window_get_next(win, &iter))
710 break;
714 bkgdset(pairs[0]);
715 memset(print_buffer, ' ', w);
716 print_buffer[w] = 0;
717 while (i < nr_rows) {
718 dump_print_buffer(y + i + 1, x);
719 i++;
723 static void update_tree_window(void)
725 update_window(lib_tree_win, tree_win_x, tree_win_y,
726 tree_win_w, "Artist / Album", print_tree);
729 static void update_track_window(void)
731 char title[512];
733 /* it doesn't matter what format options we use because the format
734 * string does not contain any format charaters */
735 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
736 update_window(lib_track_win, track_win_x, track_win_y,
737 track_win_w, title, print_track);
740 static const char *pretty(const char *path)
742 static int home_len = -1;
743 static char buf[256];
745 if (home_len == -1)
746 home_len = strlen(home_dir);
748 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
749 return path;
751 buf[0] = '~';
752 strcpy(buf + 1, path + home_len);
753 return buf;
756 static const char * const sorted_names[2] = { "", "sorted by " };
758 static void update_editable_window(struct editable *e, const char *title, const char *filename)
760 char buf[512];
761 int pos;
763 if (filename) {
764 if (using_utf8) {
765 /* already UTF-8 */
766 } else {
767 utf8_encode(filename);
768 filename = conv_buffer;
770 snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
771 pretty(filename), e->nr_tracks);
772 } else {
773 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
776 if (e->nr_marked) {
777 pos = strlen(buf);
778 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
780 pos = strlen(buf);
781 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
782 sorted_names[e->sort_str[0] != 0], e->sort_str);
784 update_window(e->win, 0, 0, COLS, buf, &print_editable);
787 static void update_sorted_window(void)
789 current_track = (struct simple_track *)lib_cur_track;
790 update_editable_window(&lib_editable, "Library", lib_filename);
793 static void update_pl_window(void)
795 current_track = pl_cur_track;
796 update_editable_window(&pl_editable, "Playlist", pl_filename);
799 static void update_play_queue_window(void)
801 current_track = NULL;
802 update_editable_window(&pq_editable, "Play Queue", NULL);
805 static void update_browser_window(void)
807 char title[512];
808 char *dirname;
810 if (using_utf8) {
811 /* already UTF-8 */
812 dirname = browser_dir;
813 } else {
814 utf8_encode(browser_dir);
815 dirname = conv_buffer;
817 snprintf(title, sizeof(title), "Browser - %s", dirname);
818 update_window(browser_win, 0, 0, COLS, title, print_browser);
821 static void update_filters_window(void)
823 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
826 static void update_help_window(void)
828 update_window(help_win, 0, 0, COLS, "Settings", print_help);
831 static void draw_separator(void)
833 int row;
835 bkgdset(pairs[CURSED_WIN_TITLE]);
836 mvaddch(0, tree_win_w, ' ');
837 bkgdset(pairs[CURSED_SEPARATOR]);
838 for (row = 1; row < LINES - 3; row++)
839 mvaddch(row, tree_win_w, ACS_VLINE);
842 static void do_update_view(int full)
844 cursor_x = -1;
845 cursor_y = -1;
847 switch (cur_view) {
848 case TREE_VIEW:
849 editable_lock();
850 if (full || lib_tree_win->changed)
851 update_tree_window();
852 if (full || lib_track_win->changed)
853 update_track_window();
854 editable_unlock();
855 draw_separator();
856 break;
857 case SORTED_VIEW:
858 editable_lock();
859 update_sorted_window();
860 editable_unlock();
861 break;
862 case PLAYLIST_VIEW:
863 editable_lock();
864 update_pl_window();
865 editable_unlock();
866 break;
867 case QUEUE_VIEW:
868 editable_lock();
869 update_play_queue_window();
870 editable_unlock();
871 break;
872 case BROWSER_VIEW:
873 update_browser_window();
874 break;
875 case FILTERS_VIEW:
876 update_filters_window();
877 break;
878 case HELP_VIEW:
879 update_help_window();
880 break;
884 static void do_update_statusline(void)
886 static const char *status_strs[] = { ".", ">", "|" };
887 static const char *cont_strs[] = { " ", "C" };
888 static const char *repeat_strs[] = { " ", "R" };
889 static const char *shuffle_strs[] = { " ", "S" };
890 int buffer_fill, vol, vol_left, vol_right;
891 int duration = -1;
892 char *msg;
893 char format[80];
895 editable_lock();
896 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
897 pl_editable.total_time, 0);
898 editable_unlock();
900 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
901 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
902 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
904 player_info_lock();
906 if (player_info.ti)
907 duration = player_info.ti->duration;
909 if (volume_max == 0) {
910 vol_left = vol_right = vol = -1;
911 } else {
912 vol_left = scale_to_percentage(player_info.vol_left, volume_max);
913 vol_right = scale_to_percentage(player_info.vol_right, volume_max);
914 vol = (vol_left + vol_right + 1) / 2;
916 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
918 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
920 if (show_remaining_time && duration != -1) {
921 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
922 } else {
923 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
926 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
927 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
928 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
929 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
930 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
931 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
933 strcpy(format, " %s %p ");
934 if (duration != -1)
935 strcat(format, "/ %d ");
936 strcat(format, "- %t ");
937 if (volume_max != 0) {
938 if (player_info.vol_left != player_info.vol_right) {
939 strcat(format, "vol: %l,%r ");
940 } else {
941 strcat(format, "vol: %v ");
944 if (player_info.ti && is_url(player_info.ti->filename))
945 strcat(format, "buf: %b ");
946 strcat(format, "%=");
947 if (play_library) {
948 /* artist/album modes work only in lib */
949 if (shuffle) {
950 /* shuffle overrides sorted mode */
951 strcat(format, "%L from library");
952 } else if (play_sorted) {
953 strcat(format, "%L from sorted library");
954 } else {
955 strcat(format, "%L from library");
957 } else {
958 strcat(format, "playlist");
960 strcat(format, " | %1C%1R%1S ");
961 format_print(print_buffer, COLS, format, status_fopts);
963 msg = player_info.error_msg;
964 player_info.error_msg = NULL;
966 player_info_unlock();
968 bkgdset(pairs[CURSED_STATUSLINE]);
969 dump_print_buffer(LINES - 2, 0);
971 if (msg) {
972 error_msg("%s", msg);
973 free(msg);
977 static void dump_buffer(const char *buffer)
979 if (using_utf8) {
980 addstr(buffer);
981 } else {
982 utf8_decode(buffer);
983 addstr(conv_buffer);
987 static void do_update_commandline(void)
989 char *str;
990 int w, idx;
991 char ch;
993 move(LINES - 1, 0);
994 if (error_buf[0]) {
995 if (msg_is_error) {
996 bkgdset(pairs[CURSED_ERROR]);
997 } else {
998 bkgdset(pairs[CURSED_INFO]);
1000 addstr(error_buf);
1001 clrtoeol();
1002 return;
1004 bkgdset(pairs[CURSED_COMMANDLINE]);
1005 if (input_mode == NORMAL_MODE) {
1006 clrtoeol();
1007 return;
1010 str = cmdline.line;
1011 if (!using_utf8) {
1012 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1013 * characters are invalid UTF-8 so it really is in locale's
1014 * encoding.
1016 * This code should be safe because cmdline.bpos ==
1017 * cmdline.cpos as every non-ASCII character is counted as one
1018 * invalid UTF-8 byte.
1020 * NOTE: This has nothing to do with widths of printed
1021 * characters. I.e. even if there were control characters
1022 * (displayed as <xx>) there would be no problem because bpos
1023 * still equals to cpos, I think.
1025 utf8_encode(cmdline.line);
1026 str = conv_buffer;
1029 /* COMMAND_MODE or SEARCH_MODE */
1030 w = u_str_width(str);
1031 ch = ':';
1032 if (input_mode == SEARCH_MODE)
1033 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
1035 if (w <= COLS - 2) {
1036 addch(ch);
1037 idx = u_copy_chars(print_buffer, str, &w);
1038 print_buffer[idx] = 0;
1039 dump_buffer(print_buffer);
1040 clrtoeol();
1041 } else {
1042 /* keep cursor as far right as possible */
1043 int skip, width, cw;
1045 /* cursor pos (width, not chars. doesn't count the ':') */
1046 cw = u_str_nwidth(str, cmdline.cpos);
1048 skip = cw + 2 - COLS;
1049 if (skip > 0) {
1050 /* skip the ':' */
1051 skip--;
1053 /* skip rest (if any) */
1054 idx = u_skip_chars(str, &skip);
1056 width = COLS;
1057 idx = u_copy_chars(print_buffer, str + idx, &width);
1058 while (width < COLS) {
1059 /* cursor is at end of the buffer
1060 * print 1, 2 or 3 spaces
1062 * To clarify:
1064 * If the last _skipped_ character was double-width we may need
1065 * to print 2 spaces.
1067 * If the last _skipped_ character was invalid UTF-8 we may need
1068 * to print 3 spaces.
1070 print_buffer[idx++] = ' ';
1071 width++;
1073 print_buffer[idx] = 0;
1074 dump_buffer(print_buffer);
1075 } else {
1076 /* print ':' + COLS - 1 chars */
1077 addch(ch);
1078 width = COLS - 1;
1079 idx = u_copy_chars(print_buffer, str, &width);
1080 print_buffer[idx] = 0;
1081 dump_buffer(print_buffer);
1086 /* lock player_info! */
1087 static const char *get_stream_title(void)
1089 static char stream_title[255 * 16 + 1];
1090 char *ptr, *title;
1092 ptr = strstr(player_info.metadata, "StreamTitle='");
1093 if (ptr == NULL)
1094 return NULL;
1095 ptr += 13;
1096 title = ptr;
1097 while (*ptr) {
1098 if (*ptr == '\'' && *(ptr + 1) == ';') {
1099 memcpy(stream_title, title, ptr - title);
1100 stream_title[ptr - title] = 0;
1101 return stream_title;
1103 ptr++;
1105 return NULL;
1108 static void set_title(const char *title)
1110 if (!set_term_title)
1111 return;
1113 if (t_ts) {
1114 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1115 fflush(stdout);
1119 static void do_update_titleline(void)
1121 bkgdset(pairs[CURSED_TITLELINE]);
1122 player_info_lock();
1123 if (player_info.ti) {
1124 int i, use_alt_format = 0;
1125 char *wtitle;
1127 fill_track_fopts_track_info(player_info.ti);
1128 if (is_url(player_info.ti->filename)) {
1129 const char *title = get_stream_title();
1131 if (title == NULL)
1132 use_alt_format = 1;
1133 fopt_set_str(&track_fopts[TF_TITLE], title);
1134 } else {
1135 use_alt_format = !track_info_has_tag(player_info.ti);
1138 if (use_alt_format) {
1139 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1140 } else {
1141 format_print(print_buffer, COLS, current_format, track_fopts);
1143 dump_print_buffer(LINES - 3, 0);
1145 /* set window title */
1146 if (use_alt_format) {
1147 format_print(print_buffer, sizeof(print_buffer) - 1,
1148 window_title_alt_format, track_fopts);
1149 } else {
1150 format_print(print_buffer, sizeof(print_buffer) - 1,
1151 window_title_format, track_fopts);
1154 /* remove whitespace */
1155 i = strlen(print_buffer) - 1;
1156 while (i > 0 && print_buffer[i] == ' ')
1157 i--;
1158 print_buffer[i + 1] = 0;
1160 if (using_utf8) {
1161 wtitle = print_buffer;
1162 } else {
1163 utf8_decode(print_buffer);
1164 wtitle = conv_buffer;
1167 set_title(wtitle);
1168 } else {
1169 move(LINES - 3, 0);
1170 clrtoeol();
1172 set_title("cmus " VERSION);
1174 player_info_unlock();
1177 static int cmdline_cursor_column(void)
1179 char *str;
1180 int cw, skip, s;
1182 str = cmdline.line;
1183 if (!using_utf8) {
1184 /* see do_update_commandline */
1185 utf8_encode(cmdline.line);
1186 str = conv_buffer;
1189 /* width of the text in the buffer before cursor */
1190 cw = u_str_nwidth(str, cmdline.cpos);
1192 if (1 + cw < COLS) {
1193 /* whole line is visible */
1194 return 1 + cw;
1197 /* beginning of cmdline is not visible */
1199 /* check if the first visible char in cmdline would be halved
1200 * double-width character (or invalid byte <xx>) which is not possible.
1201 * we need to skip the whole character and move cursor to COLS - 2
1202 * column. */
1203 skip = cw + 2 - COLS;
1205 /* skip the ':' */
1206 skip--;
1208 /* skip rest */
1209 s = skip;
1210 u_skip_chars(str, &s);
1211 if (s > skip) {
1212 /* the last skipped char was double-width or <xx> */
1213 return COLS - 1 - (s - skip);
1215 return COLS - 1;
1218 static void post_update(void)
1220 /* refresh makes cursor visible at least for urxvt */
1221 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1222 move(LINES - 1, cmdline_cursor_column());
1223 refresh();
1224 curs_set(1);
1225 } else {
1226 if (cursor_x >= 0) {
1227 move(cursor_y, cursor_x);
1228 } else {
1229 move(LINES - 1, 0);
1231 refresh();
1232 curs_set(0);
1236 void update_titleline(void)
1238 curs_set(0);
1239 do_update_titleline();
1240 post_update();
1243 void update_full(void)
1245 if (!ui_initialized)
1246 return;
1248 curs_set(0);
1250 do_update_view(1);
1251 do_update_titleline();
1252 do_update_statusline();
1253 do_update_commandline();
1255 post_update();
1258 static void update_commandline(void)
1260 curs_set(0);
1261 do_update_commandline();
1262 post_update();
1265 void update_statusline(void)
1267 if (!ui_initialized)
1268 return;
1270 curs_set(0);
1271 do_update_statusline();
1272 post_update();
1275 void info_msg(const char *format, ...)
1277 va_list ap;
1279 va_start(ap, format);
1280 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1281 va_end(ap);
1283 msg_is_error = 0;
1285 update_commandline();
1288 void error_msg(const char *format, ...)
1290 va_list ap;
1292 if (!display_errors)
1293 return;
1295 strcpy(error_buf, "Error: ");
1296 va_start(ap, format);
1297 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1298 va_end(ap);
1300 msg_is_error = 1;
1301 error_count++;
1303 if (ui_initialized) {
1304 error_time = time(NULL);
1305 update_commandline();
1306 } else {
1307 warn("%s\n", error_buf);
1308 error_buf[0] = 0;
1312 int yes_no_query(const char *format, ...)
1314 char buffer[512];
1315 va_list ap;
1316 int ret = 0;
1318 va_start(ap, format);
1319 vsnprintf(buffer, sizeof(buffer), format, ap);
1320 va_end(ap);
1322 move(LINES - 1, 0);
1323 bkgdset(pairs[CURSED_INFO]);
1325 /* no need to convert buffer.
1326 * it is always encoded in the right charset (assuming filenames are
1327 * encoded in same charset as LC_CTYPE).
1330 addstr(buffer);
1331 clrtoeol();
1332 refresh();
1334 while (1) {
1335 int ch = getch();
1337 if (ch == ERR || ch == 0)
1338 continue;
1339 if (ch == 'y')
1340 ret = 1;
1341 break;
1343 update_commandline();
1344 return ret;
1347 void search_not_found(void)
1349 const char *what = "Track";
1351 if (search_restricted) {
1352 switch (cur_view) {
1353 case TREE_VIEW:
1354 what = "Artist/album";
1355 break;
1356 case SORTED_VIEW:
1357 case PLAYLIST_VIEW:
1358 case QUEUE_VIEW:
1359 what = "Title";
1360 break;
1361 case BROWSER_VIEW:
1362 what = "File/Directory";
1363 break;
1364 case FILTERS_VIEW:
1365 what = "Filter";
1366 break;
1367 case HELP_VIEW:
1368 what = "Binding/command/option";
1369 break;
1371 } else {
1372 switch (cur_view) {
1373 case TREE_VIEW:
1374 case SORTED_VIEW:
1375 case PLAYLIST_VIEW:
1376 case QUEUE_VIEW:
1377 what = "Track";
1378 break;
1379 case BROWSER_VIEW:
1380 what = "File/Directory";
1381 break;
1382 case FILTERS_VIEW:
1383 what = "Filter";
1384 break;
1385 case HELP_VIEW:
1386 what = "Binding/command/option";
1387 break;
1390 info_msg("%s not found: %s", what, search_str ? : "");
1393 void set_view(int view)
1395 if (view == cur_view)
1396 return;
1398 cur_view = view;
1399 switch (cur_view) {
1400 case TREE_VIEW:
1401 searchable = tree_searchable;
1402 break;
1403 case SORTED_VIEW:
1404 searchable = lib_editable.searchable;
1405 break;
1406 case PLAYLIST_VIEW:
1407 searchable = pl_editable.searchable;
1408 break;
1409 case QUEUE_VIEW:
1410 searchable = pq_editable.searchable;
1411 break;
1412 case BROWSER_VIEW:
1413 searchable = browser_searchable;
1414 break;
1415 case FILTERS_VIEW:
1416 searchable = filters_searchable;
1417 break;
1418 case HELP_VIEW:
1419 searchable = help_searchable;
1420 update_help_window();
1421 break;
1424 curs_set(0);
1425 do_update_view(1);
1426 post_update();
1429 void enter_command_mode(void)
1431 error_buf[0] = 0;
1432 error_time = 0;
1433 input_mode = COMMAND_MODE;
1434 update_commandline();
1437 void enter_search_mode(void)
1439 error_buf[0] = 0;
1440 error_time = 0;
1441 input_mode = SEARCH_MODE;
1442 search_direction = SEARCH_FORWARD;
1443 update_commandline();
1446 void enter_search_backward_mode(void)
1448 error_buf[0] = 0;
1449 error_time = 0;
1450 input_mode = SEARCH_MODE;
1451 search_direction = SEARCH_BACKWARD;
1452 update_commandline();
1455 void quit(void)
1457 running = 0;
1460 void update_colors(void)
1462 int i;
1464 if (!ui_initialized)
1465 return;
1467 for (i = 0; i < NR_CURSED; i++) {
1468 int bg = colors[cursed_to_bg_idx[i]];
1469 int fg = colors[cursed_to_fg_idx[i]];
1470 int pair = i + 1;
1472 if (fg >= 8 && fg <= 15) {
1473 /* fg colors 8..15 are special (0..7 + bold) */
1474 init_pair(pair, fg & 7, bg);
1475 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1476 } else {
1477 init_pair(pair, fg, bg);
1478 pairs[i] = COLOR_PAIR(pair);
1483 static void clear_error(void)
1485 time_t t = time(NULL);
1487 /* error msg is visible at least 3s */
1488 if (t - error_time < 3)
1489 return;
1491 if (error_buf[0]) {
1492 error_time = 0;
1493 error_buf[0] = 0;
1494 update_commandline();
1498 /* screen updates }}} */
1500 static void spawn_status_program(void)
1502 static const char *status_strs[] = { "stopped", "playing", "paused" };
1503 const char *stream_title = NULL;
1504 char *argv[32];
1505 int i, status;
1507 if (status_display_program == NULL || status_display_program[0] == 0)
1508 return;
1510 player_info_lock();
1511 status = player_info.status;
1512 if (status == 1)
1513 stream_title = get_stream_title();
1515 i = 0;
1516 argv[i++] = xstrdup(status_display_program);
1518 argv[i++] = xstrdup("status");
1519 argv[i++] = xstrdup(status_strs[status]);
1520 if (player_info.ti) {
1521 static const char *keys[] = {
1522 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1524 int j;
1526 if (is_url(player_info.ti->filename)) {
1527 argv[i++] = xstrdup("url");
1528 argv[i++] = xstrdup(player_info.ti->filename);
1529 if (stream_title) {
1530 argv[i++] = xstrdup("title");
1531 argv[i++] = xstrdup(stream_title);
1533 } else {
1534 char buf[32];
1536 argv[i++] = xstrdup("file");
1537 argv[i++] = xstrdup(player_info.ti->filename);
1538 for (j = 0; keys[j]; j++) {
1539 const char *key = keys[j];
1540 const char *val;
1542 val = comments_get_val(player_info.ti->comments, key);
1543 if (val) {
1544 argv[i++] = xstrdup(key);
1545 argv[i++] = xstrdup(val);
1548 snprintf(buf, sizeof(buf), "%d", player_info.ti->duration);
1549 argv[i++] = xstrdup("duration");
1550 argv[i++] = xstrdup(buf);
1553 argv[i++] = NULL;
1554 player_info_unlock();
1556 if (spawn(argv, &status) == -1)
1557 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1558 for (i = 0; argv[i]; i++)
1559 free(argv[i]);
1562 static int ctrl_c_pressed = 0;
1564 static void sig_int(int sig)
1566 ctrl_c_pressed = 1;
1569 static int needs_to_resize = 1;
1571 static void sig_winch(int sig)
1573 needs_to_resize = 1;
1576 static int get_window_size(int *lines, int *columns)
1578 struct winsize ws;
1580 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1581 return -1;
1582 *columns = ws.ws_col;
1583 *lines = ws.ws_row;
1584 return 0;
1587 static void resize_tree_view(int w, int h)
1589 tree_win_w = w / 3;
1590 track_win_w = w - tree_win_w - 1;
1591 if (tree_win_w < 8)
1592 tree_win_w = 8;
1593 if (track_win_w < 8)
1594 track_win_w = 8;
1595 tree_win_x = 0;
1596 tree_win_y = 0;
1597 track_win_x = tree_win_w + 1;
1598 track_win_y = 0;
1600 h--;
1601 window_set_nr_rows(lib_tree_win, h);
1602 window_set_nr_rows(lib_track_win, h);
1605 static void update(void)
1607 int needs_view_update = 0;
1608 int needs_title_update = 0;
1609 int needs_status_update = 0;
1610 int needs_command_update = 0;
1611 int needs_spawn = 0;
1613 if (needs_to_resize) {
1614 int w, h;
1615 int columns, lines;
1617 if (get_window_size(&lines, &columns) == 0) {
1618 needs_to_resize = 0;
1619 resizeterm(lines, columns);
1620 w = COLS;
1621 h = LINES - 3;
1622 if (w < 16)
1623 w = 16;
1624 if (h < 8)
1625 h = 8;
1626 editable_lock();
1627 resize_tree_view(w, h);
1628 window_set_nr_rows(lib_editable.win, h - 1);
1629 window_set_nr_rows(pl_editable.win, h - 1);
1630 window_set_nr_rows(pq_editable.win, h - 1);
1631 window_set_nr_rows(filters_win, h - 1);
1632 window_set_nr_rows(help_win, h - 1);
1633 window_set_nr_rows(browser_win, h - 1);
1634 editable_unlock();
1635 needs_title_update = 1;
1636 needs_status_update = 1;
1637 needs_command_update = 1;
1641 player_info_lock();
1642 editable_lock();
1644 needs_spawn = player_info.status_changed || player_info.file_changed ||
1645 player_info.metadata_changed;
1647 if (player_info.file_changed) {
1648 player_info.file_changed = 0;
1649 needs_title_update = 1;
1650 needs_status_update = 1;
1652 if (player_info.metadata_changed) {
1653 player_info.metadata_changed = 0;
1654 needs_title_update = 1;
1656 if (player_info.position_changed || player_info.status_changed || player_info.vol_changed) {
1657 player_info.position_changed = 0;
1658 player_info.status_changed = 0;
1659 player_info.vol_changed = 0;
1661 needs_status_update = 1;
1663 switch (cur_view) {
1664 case TREE_VIEW:
1665 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
1666 break;
1667 case SORTED_VIEW:
1668 needs_view_update += lib_editable.win->changed;
1669 break;
1670 case PLAYLIST_VIEW:
1671 needs_view_update += pl_editable.win->changed;
1672 break;
1673 case QUEUE_VIEW:
1674 needs_view_update += pq_editable.win->changed;
1675 break;
1676 case BROWSER_VIEW:
1677 needs_view_update += browser_win->changed;
1678 break;
1679 case FILTERS_VIEW:
1680 needs_view_update += filters_win->changed;
1681 break;
1682 case HELP_VIEW:
1683 needs_view_update += help_win->changed;
1684 break;
1687 /* total time changed? */
1688 if (play_library) {
1689 needs_status_update += lib_editable.win->changed;
1690 lib_editable.win->changed = 0;
1691 } else {
1692 needs_status_update += pl_editable.win->changed;
1693 lib_editable.win->changed = 0;
1696 editable_unlock();
1697 player_info_unlock();
1699 if (needs_spawn)
1700 spawn_status_program();
1702 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1703 curs_set(0);
1705 if (needs_view_update)
1706 do_update_view(0);
1707 if (needs_title_update)
1708 do_update_titleline();
1709 if (needs_status_update)
1710 do_update_statusline();
1711 if (needs_command_update)
1712 do_update_commandline();
1713 post_update();
1717 static void handle_ch(uchar ch)
1719 clear_error();
1720 if (input_mode == NORMAL_MODE) {
1721 normal_mode_ch(ch);
1722 } else if (input_mode == COMMAND_MODE) {
1723 command_mode_ch(ch);
1724 update_commandline();
1725 } else if (input_mode == SEARCH_MODE) {
1726 search_mode_ch(ch);
1727 update_commandline();
1731 static void handle_key(int key)
1733 clear_error();
1734 if (input_mode == NORMAL_MODE) {
1735 normal_mode_key(key);
1736 } else if (input_mode == COMMAND_MODE) {
1737 command_mode_key(key);
1738 update_commandline();
1739 } else if (input_mode == SEARCH_MODE) {
1740 search_mode_key(key);
1741 update_commandline();
1745 static void u_getch(void)
1747 int key;
1748 int bit = 7;
1749 int mask = (1 << 7);
1750 uchar u, ch;
1752 key = getch();
1753 if (key == ERR || key == 0)
1754 return;
1756 if (key > 255) {
1757 handle_key(key);
1758 return;
1761 ch = (unsigned char)key;
1762 while (bit > 0 && ch & mask) {
1763 mask >>= 1;
1764 bit--;
1766 if (bit == 7) {
1767 /* ascii */
1768 u = ch;
1769 } else {
1770 int count;
1772 u = ch & ((1 << bit) - 1);
1773 count = 6 - bit;
1774 while (count) {
1775 key = getch();
1776 if (key == ERR || key == 0)
1777 return;
1779 ch = (unsigned char)key;
1780 u = (u << 6) | (ch & 63);
1781 count--;
1784 handle_ch(u);
1787 static void main_loop(void)
1789 int rc, fd_high;
1791 fd_high = server_socket;
1792 while (running) {
1793 fd_set set;
1794 struct timeval tv;
1796 update();
1798 FD_ZERO(&set);
1799 FD_SET(0, &set);
1800 FD_SET(server_socket, &set);
1802 /* Timeout must be so small that screen updates seem instant.
1803 * Only affects changes done in other threads (worker, player).
1805 * Too small timeout makes window updates too fast (wastes CPU).
1807 * Too large timeout makes status line (position) updates too slow.
1808 * The timeout is accuracy of player position.
1810 tv.tv_sec = 0;
1811 tv.tv_usec = 100e3;
1812 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1813 if (rc <= 0) {
1814 if (ctrl_c_pressed) {
1815 handle_ch(0x03);
1816 ctrl_c_pressed = 0;
1818 continue;
1821 if (FD_ISSET(server_socket, &set)) {
1822 /* no error msgs for cmus-remote */
1823 display_errors = 0;
1825 server_serve();
1827 if (FD_ISSET(0, &set)) {
1828 /* diplay errors for interactive commands */
1829 display_errors = 1;
1831 if (using_utf8) {
1832 u_getch();
1833 } else {
1834 int key = getch();
1836 if (key != ERR && key != 0) {
1837 if (key > 255) {
1838 handle_key(key);
1839 } else {
1840 uchar ch = key;
1842 if (ch > 0x7f)
1843 ch |= U_INVALID_MASK;
1844 handle_ch(ch);
1852 static int get_next(struct track_info **ti)
1854 struct track_info *info;
1856 editable_lock();
1857 info = play_queue_remove();
1858 if (info == NULL) {
1859 if (play_library) {
1860 info = lib_set_next();
1861 } else {
1862 info = pl_set_next();
1865 editable_unlock();
1867 if (info == NULL)
1868 return -1;
1870 *ti = info;
1871 return 0;
1874 static const struct player_callbacks player_callbacks = {
1875 .get_next = get_next
1878 static void init_curses(void)
1880 struct sigaction act;
1881 char *ptr, *term;
1883 sigemptyset(&act.sa_mask);
1884 act.sa_flags = 0;
1885 act.sa_handler = sig_int;
1886 sigaction(SIGINT, &act, NULL);
1888 sigemptyset(&act.sa_mask);
1889 act.sa_flags = 0;
1890 act.sa_handler = sig_winch;
1891 sigaction(SIGWINCH, &act, NULL);
1893 initscr();
1895 /* turn off kb buffering */
1896 cbreak();
1898 keypad(stdscr, TRUE);
1900 /* wait max 5 * 0.1 s if there are no keys available
1901 * doesn't really matter because we use select()
1903 halfdelay(5);
1905 noecho();
1906 if (has_colors()) {
1907 start_color();
1908 use_default_colors();
1910 d_print("Number of supported colors: %d\n", COLORS);
1911 ui_initialized = 1;
1913 /* this was disabled while initializing because it needs to be
1914 * called only once after all colors have been set
1916 update_colors();
1918 ptr = print_buffer;
1919 t_ts = tgetstr("ts", &ptr);
1920 t_fs = tgetstr("fs", &ptr);
1921 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
1923 if (!t_fs)
1924 t_ts = NULL;
1926 if (!t_ts && (term = getenv("TERM"))) {
1928 * Eterm: Eterm
1929 * aterm: rxvt
1930 * mlterm: xterm
1931 * terminal (xfce): xterm
1932 * urxvt: rxvt-unicode
1933 * xterm: xterm, xterm-{,16,88,256}color
1935 if (!strcmp(term, "screen")) {
1936 t_ts = "\033_";
1937 t_fs = "\033\\";
1938 } else if (!strncmp(term, "xterm", 5) ||
1939 !strncmp(term, "rxvt", 4) ||
1940 !strcmp(term, "Eterm")) {
1941 /* \033]1; change icon
1942 * \033]2; change title
1943 * \033]0; change both
1945 t_ts = "\033]0;";
1946 t_fs = "\007";
1951 static void init_all(void)
1953 server_init(server_address);
1955 /* does not select output plugin */
1956 player_init(&player_callbacks);
1958 /* plugins have been loaded so we know what plugin options are available */
1959 options_add();
1961 lib_init();
1962 searchable = tree_searchable;
1963 pl_init();
1964 cmus_init();
1965 browser_init();
1966 filters_init();
1967 help_init();
1968 cmdline_init();
1969 commands_init();
1970 search_mode_init();
1972 /* almost everything must be initialized now */
1973 options_load();
1975 /* finally we can set the output plugin */
1976 player_set_op(output_plugin);
1978 player_get_volume(&player_info.vol_left, &player_info.vol_right);
1980 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
1981 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
1982 pl_filename = xstrdup(pl_autosave_filename);
1983 lib_filename = xstrdup(lib_autosave_filename);
1985 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
1986 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
1988 if (error_count) {
1989 char buf[16];
1991 warn("Press <enter> to continue.");
1992 fgets(buf, sizeof(buf), stdin);
1994 help_add_all_unbound();
1996 init_curses();
1999 static void exit_all(void)
2001 endwin();
2003 options_exit();
2005 server_exit();
2006 cmus_exit();
2007 cmus_save(lib_for_each, lib_autosave_filename);
2008 cmus_save(pl_for_each, pl_autosave_filename);
2010 player_exit();
2011 commands_exit();
2012 search_mode_exit();
2013 filters_exit();
2014 help_exit();
2015 browser_exit();
2018 enum {
2019 FLAG_LISTEN,
2020 FLAG_PLUGINS,
2021 FLAG_HELP,
2022 FLAG_VERSION,
2023 NR_FLAGS
2026 static struct option options[NR_FLAGS + 1] = {
2027 { 0, "listen", 1 },
2028 { 0, "plugins", 0 },
2029 { 0, "help", 0 },
2030 { 0, "version", 0 },
2031 { 0, NULL, 0 }
2034 static const char *usage =
2035 "Usage: %s [OPTION]...\n"
2036 "Curses based music player.\n"
2037 "\n"
2038 " --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
2039 " ADDR is either a UNIX socket or host[:port]\n"
2040 " --plugins list available plugins and exit\n"
2041 " --help display this help and exit\n"
2042 " --version " VERSION "\n"
2043 "\n"
2044 "Use cmus-remote to control cmus from command line.\n"
2045 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2047 int main(int argc, char *argv[])
2049 int list_plugins = 0;
2051 program_name = argv[0];
2052 argv++;
2053 while (1) {
2054 int idx;
2055 char *arg;
2057 idx = get_option(&argv, options, &arg);
2058 if (idx < 0)
2059 break;
2061 switch (idx) {
2062 case FLAG_HELP:
2063 printf(usage, program_name);
2064 return 0;
2065 case FLAG_VERSION:
2066 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
2067 return 0;
2068 case FLAG_PLUGINS:
2069 list_plugins = 1;
2070 break;
2071 case FLAG_LISTEN:
2072 server_address = xstrdup(arg);
2073 break;
2077 setlocale(LC_CTYPE, "");
2078 #ifdef CODESET
2079 charset = nl_langinfo(CODESET);
2080 #else
2081 charset = "ISO-8859-1";
2082 #endif
2083 if (strcmp(charset, "UTF-8") == 0) {
2084 using_utf8 = 1;
2085 } else {
2086 using_utf8 = 0;
2088 misc_init();
2089 if (server_address == NULL)
2090 server_address = xstrjoin(cmus_config_dir, "/socket");
2091 debug_init();
2092 d_print("charset = '%s'\n", charset);
2094 player_load_plugins();
2095 if (list_plugins) {
2096 player_dump_plugins();
2097 return 0;
2099 init_all();
2100 main_loop();
2101 exit_all();
2102 return 0;