Handle streams separately in tree_add_track()
[cmus.git] / ui_curses.c
blob348cde377f04bdff7ffcd9687792d06a3cc2542e
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 "output.h"
31 #include "utils.h"
32 #include "lib.h"
33 #include "pl.h"
34 #include "xmalloc.h"
35 #include "xstrjoin.h"
36 #include "window.h"
37 #include "format_print.h"
38 #include "misc.h"
39 #include "prog.h"
40 #include "uchar.h"
41 #include "spawn.h"
42 #include "server.h"
43 #include "keys.h"
44 #include "debug.h"
45 #include "help.h"
46 #include "worker.h"
47 #include "input.h"
49 #include <unistd.h>
50 #include <stdlib.h>
51 #include <stdio.h>
52 #include <errno.h>
53 #include <sys/ioctl.h>
54 #include <ctype.h>
55 #include <dirent.h>
56 #include <locale.h>
57 #include <langinfo.h>
58 #include <iconv.h>
59 #include <signal.h>
60 #include <stdarg.h>
62 #if defined(__sun__) || defined(__CYGWIN__)
63 /* TIOCGWINSZ */
64 #include <termios.h>
65 #include <ncurses.h>
66 #else
67 #include <curses.h>
68 #endif
70 /* defined in <term.h> but without const */
71 char *tgetstr(const char *id, char **area);
72 char *tgoto(const char *cap, int col, int row);
74 /* globals. documented in ui_curses.h */
76 int cmus_running = 1;
77 int ui_initialized = 0;
78 enum ui_input_mode input_mode = NORMAL_MODE;
79 int cur_view = TREE_VIEW;
80 struct searchable *searchable;
81 char *lib_filename = NULL;
82 char *pl_filename = NULL;
84 /* ------------------------------------------------------------------------- */
86 static char *lib_autosave_filename;
87 static char *pl_autosave_filename;
89 /* shown error message and time stamp
90 * error is cleared if it is older than 3s and key was pressed
92 static char error_buf[512];
93 static time_t error_time = 0;
94 /* info messages are displayed in different color */
95 static int msg_is_error;
96 static int error_count = 0;
98 static char *server_address = NULL;
100 static char *charset = NULL;
101 static char print_buffer[512];
103 /* destination buffer for utf8_encode and utf8_decode */
104 static char conv_buffer[512];
106 #define print_buffer_size (sizeof(print_buffer) - 1)
107 static int using_utf8;
109 static const char *t_ts;
110 static const char *t_fs;
112 static int tree_win_x = 0;
113 static int tree_win_y = 0;
114 static int tree_win_w = 0;
116 static int track_win_x = 0;
117 static int track_win_y = 0;
118 static int track_win_w = 0;
120 static int cursor_x;
121 static int cursor_y;
123 enum {
124 CURSED_WIN,
125 CURSED_WIN_CUR,
126 CURSED_WIN_SEL,
127 CURSED_WIN_SEL_CUR,
129 CURSED_WIN_ACTIVE,
130 CURSED_WIN_ACTIVE_CUR,
131 CURSED_WIN_ACTIVE_SEL,
132 CURSED_WIN_ACTIVE_SEL_CUR,
134 CURSED_SEPARATOR,
135 CURSED_WIN_TITLE,
136 CURSED_COMMANDLINE,
137 CURSED_STATUSLINE,
139 CURSED_TITLELINE,
140 CURSED_DIR,
141 CURSED_ERROR,
142 CURSED_INFO,
144 NR_CURSED
147 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
148 COLOR_WIN_BG,
149 COLOR_WIN_BG,
150 COLOR_WIN_INACTIVE_SEL_BG,
151 COLOR_WIN_INACTIVE_CUR_SEL_BG,
153 COLOR_WIN_BG,
154 COLOR_WIN_BG,
155 COLOR_WIN_SEL_BG,
156 COLOR_WIN_CUR_SEL_BG,
158 COLOR_WIN_BG,
159 COLOR_WIN_TITLE_BG,
160 COLOR_CMDLINE_BG,
161 COLOR_STATUSLINE_BG,
163 COLOR_TITLELINE_BG,
164 COLOR_WIN_BG,
165 COLOR_CMDLINE_BG,
166 COLOR_CMDLINE_BG
169 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
170 COLOR_WIN_FG,
171 COLOR_WIN_CUR,
172 COLOR_WIN_INACTIVE_SEL_FG,
173 COLOR_WIN_INACTIVE_CUR_SEL_FG,
175 COLOR_WIN_FG,
176 COLOR_WIN_CUR,
177 COLOR_WIN_SEL_FG,
178 COLOR_WIN_CUR_SEL_FG,
180 COLOR_SEPARATOR,
181 COLOR_WIN_TITLE_FG,
182 COLOR_CMDLINE_FG,
183 COLOR_STATUSLINE_FG,
185 COLOR_TITLELINE_FG,
186 COLOR_WIN_DIR,
187 COLOR_ERROR,
188 COLOR_INFO
191 /* index is CURSED_*, value is fucking color pair */
192 static int pairs[NR_CURSED];
194 enum {
195 TF_ARTIST,
196 TF_ALBUM,
197 TF_DISC,
198 TF_TRACK,
199 TF_TITLE,
200 TF_YEAR,
201 TF_GENRE,
202 TF_COMMENT,
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_STR('c'),
218 DEF_FO_TIME('d'),
219 DEF_FO_STR('f'),
220 DEF_FO_STR('F'),
221 DEF_FO_END
224 enum {
225 SF_STATUS,
226 SF_POSITION,
227 SF_DURATION,
228 SF_TOTAL,
229 SF_VOLUME,
230 SF_LVOLUME,
231 SF_RVOLUME,
232 SF_BUFFER,
233 SF_REPEAT,
234 SF_CONTINUE,
235 SF_SHUFFLE,
236 SF_PLAYLISTMODE,
237 NR_SFS
240 static struct format_option status_fopts[NR_SFS + 1] = {
241 DEF_FO_STR('s'),
242 DEF_FO_TIME('p'),
243 DEF_FO_TIME('d'),
244 DEF_FO_TIME('t'),
245 DEF_FO_INT('v'),
246 DEF_FO_INT('l'),
247 DEF_FO_INT('r'),
248 DEF_FO_INT('b'),
249 DEF_FO_STR('R'),
250 DEF_FO_STR('C'),
251 DEF_FO_STR('S'),
252 DEF_FO_STR('L'),
253 DEF_FO_END
256 static void utf8_encode(const char *buffer)
258 static iconv_t cd = (iconv_t)-1;
259 size_t is, os;
260 const char *i;
261 char *o;
262 int rc;
264 if (cd == (iconv_t)-1) {
265 d_print("iconv_open(UTF-8, %s)\n", charset);
266 cd = iconv_open("UTF-8", charset);
267 if (cd == (iconv_t)-1) {
268 d_print("iconv_open failed: %s\n", strerror(errno));
269 return;
272 i = buffer;
273 o = conv_buffer;
274 is = strlen(i);
275 os = sizeof(conv_buffer) - 1;
276 rc = iconv(cd, (void *)&i, &is, &o, &os);
277 *o = 0;
278 if (rc == -1) {
279 d_print("iconv failed: %s\n", strerror(errno));
280 return;
284 static void utf8_decode(const char *buffer)
286 static iconv_t cd = (iconv_t)-1;
287 size_t is, os;
288 const char *i;
289 char *o;
290 int rc;
292 if (cd == (iconv_t)-1) {
293 d_print("iconv_open(%s, UTF-8)\n", charset);
294 cd = iconv_open(charset, "UTF-8");
295 if (cd == (iconv_t)-1) {
296 d_print("iconv_open failed: %s\n", strerror(errno));
297 return;
300 i = buffer;
301 o = conv_buffer;
302 is = strlen(i);
303 os = sizeof(conv_buffer) - 1;
304 rc = iconv(cd, (void *)&i, &is, &o, &os);
305 *o = 0;
306 if (rc == -1) {
307 d_print("iconv failed: %s\n", strerror(errno));
308 return;
312 /* screen updates {{{ */
314 static void dump_print_buffer(int row, int col)
316 if (using_utf8) {
317 mvaddstr(row, col, print_buffer);
318 } else {
319 utf8_decode(print_buffer);
320 mvaddstr(row, col, conv_buffer);
324 /* print @str into @buf
326 * if @str is shorter than @width pad with spaces
327 * if @str is wider than @width truncate and add "..."
329 static int format_str(char *buf, const char *str, int width)
331 int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;
333 while (1) {
334 uchar u;
335 int w;
337 u_get_char(str, &s, &u);
338 if (u == 0) {
339 memset(buf + d, ' ', width);
340 d += width;
341 break;
344 w = u_char_width(u);
345 if (width == 3)
346 ellipsis_pos = d;
347 if (width == 4 && w == 2) {
348 /* can't cut double-width char */
349 ellipsis_pos = d + 1;
350 cut_double_width = 1;
353 width -= w;
354 if (width < 0) {
355 /* does not fit */
356 d = ellipsis_pos;
357 if (cut_double_width) {
358 /* first half of the double-width char */
359 buf[d - 1] = ' ';
361 buf[d++] = '.';
362 buf[d++] = '.';
363 buf[d++] = '.';
364 break;
366 u_set_char(buf, &d, u);
368 return d;
371 static void sprint(int row, int col, const char *str, int width)
373 int pos = 0;
375 print_buffer[pos++] = ' ';
376 pos += format_str(print_buffer + pos, str, width - 2);
377 print_buffer[pos++] = ' ';
378 print_buffer[pos] = 0;
379 dump_print_buffer(row, col);
382 static void sprint_ascii(int row, int col, const char *str, int len)
384 int l;
386 l = strlen(str);
387 len -= 2;
389 print_buffer[0] = ' ';
390 if (l > len) {
391 memcpy(print_buffer + 1, str, len - 3);
392 print_buffer[len - 2] = '.';
393 print_buffer[len - 1] = '.';
394 print_buffer[len - 0] = '.';
395 } else {
396 memcpy(print_buffer + 1, str, l);
397 memset(print_buffer + 1 + l, ' ', len - l);
399 print_buffer[len + 1] = ' ';
400 print_buffer[len + 2] = 0;
401 mvaddstr(row, col, print_buffer);
404 static void print_tree(struct window *win, int row, struct iter *iter)
406 const char *str;
407 struct artist *artist;
408 struct album *album;
409 struct iter sel;
410 int current, selected, active, pos;
412 artist = iter_to_artist(iter);
413 album = iter_to_album(iter);
414 current = 0;
415 if (lib_cur_track) {
416 if (album) {
417 current = CUR_ALBUM == album;
418 } else {
419 current = CUR_ARTIST == artist;
422 window_get_sel(win, &sel);
423 selected = iters_equal(iter, &sel);
424 active = lib_cur_win == lib_tree_win;
425 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
427 if (active && selected) {
428 cursor_x = 0;
429 cursor_y = 1 + row;
432 pos = 0;
433 print_buffer[pos++] = ' ';
434 str = artist->name;
435 if (album) {
436 print_buffer[pos++] = ' ';
437 print_buffer[pos++] = ' ';
438 str = album->name;
440 pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
441 print_buffer[pos++] = ' ';
442 print_buffer[pos++] = 0;
443 dump_print_buffer(tree_win_y + row + 1, tree_win_x);
446 static inline void fopt_set_str(struct format_option *fopt, const char *str)
448 BUG_ON(fopt->type != FO_STR);
449 if (str) {
450 fopt->fo_str = str;
451 fopt->empty = 0;
452 } else {
453 fopt->empty = 1;
457 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
459 BUG_ON(fopt->type != FO_INT);
460 fopt->fo_int = value;
461 fopt->empty = empty;
464 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
466 BUG_ON(fopt->type != FO_TIME);
467 fopt->fo_time = value;
468 fopt->empty = empty;
471 static void fill_track_fopts_track_info(struct track_info *info)
473 char *filename;
474 int num, disc;
476 if (using_utf8) {
477 filename = info->filename;
478 } else {
479 utf8_encode(info->filename);
480 filename = conv_buffer;
482 disc = comments_get_int(info->comments, "discnumber");
483 num = comments_get_int(info->comments, "tracknumber");
485 fopt_set_str(&track_fopts[TF_ARTIST], keyvals_get_val(info->comments, "artist"));
486 fopt_set_str(&track_fopts[TF_ALBUM], keyvals_get_val(info->comments, "album"));
487 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
488 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
489 fopt_set_str(&track_fopts[TF_TITLE], keyvals_get_val(info->comments, "title"));
490 fopt_set_str(&track_fopts[TF_YEAR], keyvals_get_val(info->comments, "date"));
491 fopt_set_str(&track_fopts[TF_GENRE], keyvals_get_val(info->comments, "genre"));
492 fopt_set_str(&track_fopts[TF_COMMENT], keyvals_get_val(info->comments, "comment"));
493 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
494 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
495 if (is_url(info->filename)) {
496 fopt_set_str(&track_fopts[TF_FILE], filename);
497 } else {
498 const char *f;
500 f = strrchr(filename, '/');
501 if (f) {
502 fopt_set_str(&track_fopts[TF_FILE], f + 1);
503 } else {
504 fopt_set_str(&track_fopts[TF_FILE], filename);
509 static void print_track(struct window *win, int row, struct iter *iter)
511 struct tree_track *track;
512 struct iter sel;
513 int current, selected, active;
515 track = iter_to_tree_track(iter);
516 current = lib_cur_track == track;
517 window_get_sel(win, &sel);
518 selected = iters_equal(iter, &sel);
519 active = lib_cur_win == lib_track_win;
520 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
522 if (active && selected) {
523 cursor_x = track_win_x;
524 cursor_y = 1 + row;
527 fill_track_fopts_track_info(tree_track_info(track));
529 if (track_info_has_tag(tree_track_info(track))) {
530 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
531 } else {
532 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
534 dump_print_buffer(track_win_y + row + 1, track_win_x);
537 /* used by print_editable only */
538 static struct simple_track *current_track;
540 static void print_editable(struct window *win, int row, struct iter *iter)
542 struct simple_track *track;
543 struct iter sel;
544 int current, selected, active;
546 track = iter_to_simple_track(iter);
547 current = current_track == track;
548 window_get_sel(win, &sel);
549 selected = iters_equal(iter, &sel);
551 if (selected) {
552 cursor_x = 0;
553 cursor_y = 1 + row;
556 active = 1;
557 if (!selected && track->marked) {
558 selected = 1;
559 active = 0;
562 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
564 fill_track_fopts_track_info(track->info);
566 if (track_info_has_tag(track->info)) {
567 format_print(print_buffer, COLS, list_win_format, track_fopts);
568 } else {
569 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
571 dump_print_buffer(row + 1, 0);
574 static void print_browser(struct window *win, int row, struct iter *iter)
576 struct browser_entry *e;
577 struct iter sel;
578 int selected;
580 e = iter_to_browser_entry(iter);
581 window_get_sel(win, &sel);
582 selected = iters_equal(iter, &sel);
583 if (selected) {
584 int active = 1;
585 int current = 0;
587 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
588 } else {
589 if (e->type == BROWSER_ENTRY_DIR) {
590 bkgdset(pairs[CURSED_DIR]);
591 } else {
592 bkgdset(pairs[CURSED_WIN]);
596 if (selected) {
597 cursor_x = 0;
598 cursor_y = 1 + row;
601 /* file name encoding == terminal encoding. no need to convert */
602 if (using_utf8) {
603 sprint(row + 1, 0, e->name, COLS);
604 } else {
605 sprint_ascii(row + 1, 0, e->name, COLS);
609 static void print_filter(struct window *win, int row, struct iter *iter)
611 char buf[256];
612 struct filter_entry *e = iter_to_filter_entry(iter);
613 struct iter sel;
614 /* window active? */
615 int active = 1;
616 /* row selected? */
617 int selected;
618 /* is the filter currently active? */
619 int current = !!e->act_stat;
620 const char stat_chars[3] = " *!";
621 int ch1, ch2, ch3, pos;
623 window_get_sel(win, &sel);
624 selected = iters_equal(iter, &sel);
625 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
627 if (selected) {
628 cursor_x = 0;
629 cursor_y = 1 + row;
632 ch1 = ' ';
633 ch3 = ' ';
634 if (e->sel_stat != e->act_stat) {
635 ch1 = '[';
636 ch3 = ']';
638 ch2 = stat_chars[e->sel_stat];
639 snprintf(buf, sizeof(buf), "%c%c%c%-15s %s", ch1, ch2, ch3, e->name, e->filter);
640 pos = format_str(print_buffer, buf, COLS - 1);
641 print_buffer[pos++] = ' ';
642 print_buffer[pos] = 0;
643 dump_print_buffer(row + 1, 0);
646 static void print_help(struct window *win, int row, struct iter *iter)
648 struct iter sel;
649 int selected;
650 int pos;
651 int active = 1;
652 char buf[512];
653 const struct help_entry *e = iter_to_help_entry(iter);
654 const struct cmus_opt *opt;
656 window_get_sel(win, &sel);
657 selected = iters_equal(iter, &sel);
658 bkgdset(pairs[(active << 2) | (selected << 1)]);
660 if (selected) {
661 cursor_x = 0;
662 cursor_y = 1 + row;
665 switch (e->type) {
666 case HE_TEXT:
667 snprintf(buf, sizeof(buf), " %s", e->text);
668 break;
669 case HE_BOUND:
670 snprintf(buf, sizeof(buf), " %-8s %-14s %s",
671 key_context_names[e->binding->ctx],
672 e->binding->key->name,
673 e->binding->cmd);
674 break;
675 case HE_UNBOUND:
676 snprintf(buf, sizeof(buf), " %s", e->command->name);
677 break;
678 case HE_OPTION:
679 opt = e->option;
680 snprintf(buf, sizeof(buf), " %-29s ", opt->name);
681 opt->get(opt->id, buf + strlen(buf));
682 break;
684 pos = format_str(print_buffer, buf, COLS - 1);
685 print_buffer[pos++] = ' ';
686 print_buffer[pos] = 0;
687 dump_print_buffer(row + 1, 0);
690 static void update_window(struct window *win, int x, int y, int w, const char *title,
691 void (*print)(struct window *, int, struct iter *))
693 struct iter iter;
694 int nr_rows;
695 int c, i;
697 win->changed = 0;
699 bkgdset(pairs[CURSED_WIN_TITLE]);
700 c = snprintf(print_buffer, w + 1, " %s", title);
701 if (c > w)
702 c = w;
703 memset(print_buffer + c, ' ', w - c + 1);
704 print_buffer[w] = 0;
705 dump_print_buffer(y, x);
706 nr_rows = window_get_nr_rows(win);
707 i = 0;
708 if (window_get_top(win, &iter)) {
709 while (i < nr_rows) {
710 print(win, i, &iter);
711 i++;
712 if (!window_get_next(win, &iter))
713 break;
717 bkgdset(pairs[0]);
718 memset(print_buffer, ' ', w);
719 print_buffer[w] = 0;
720 while (i < nr_rows) {
721 dump_print_buffer(y + i + 1, x);
722 i++;
726 static void update_tree_window(void)
728 update_window(lib_tree_win, tree_win_x, tree_win_y,
729 tree_win_w, "Artist / Album", print_tree);
732 static void update_track_window(void)
734 char title[512];
736 /* it doesn't matter what format options we use because the format
737 * string does not contain any format charaters */
738 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
739 update_window(lib_track_win, track_win_x, track_win_y,
740 track_win_w, title, print_track);
743 static const char *pretty(const char *path)
745 static int home_len = -1;
746 static char buf[256];
748 if (home_len == -1)
749 home_len = strlen(home_dir);
751 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
752 return path;
754 buf[0] = '~';
755 strcpy(buf + 1, path + home_len);
756 return buf;
759 static const char * const sorted_names[2] = { "", "sorted by " };
761 static void update_editable_window(struct editable *e, const char *title, const char *filename)
763 char buf[512];
764 int pos;
766 if (filename) {
767 if (using_utf8) {
768 /* already UTF-8 */
769 } else {
770 utf8_encode(filename);
771 filename = conv_buffer;
773 snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
774 pretty(filename), e->nr_tracks);
775 } else {
776 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
779 if (e->nr_marked) {
780 pos = strlen(buf);
781 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
783 pos = strlen(buf);
784 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
785 sorted_names[e->sort_str[0] != 0], e->sort_str);
787 update_window(e->win, 0, 0, COLS, buf, &print_editable);
790 static void update_sorted_window(void)
792 current_track = (struct simple_track *)lib_cur_track;
793 update_editable_window(&lib_editable, "Library", lib_filename);
796 static void update_pl_window(void)
798 current_track = pl_cur_track;
799 update_editable_window(&pl_editable, "Playlist", pl_filename);
802 static void update_play_queue_window(void)
804 current_track = NULL;
805 update_editable_window(&pq_editable, "Play Queue", NULL);
808 static void update_browser_window(void)
810 char title[512];
811 char *dirname;
813 if (using_utf8) {
814 /* already UTF-8 */
815 dirname = browser_dir;
816 } else {
817 utf8_encode(browser_dir);
818 dirname = conv_buffer;
820 snprintf(title, sizeof(title), "Browser - %s", dirname);
821 update_window(browser_win, 0, 0, COLS, title, print_browser);
824 static void update_filters_window(void)
826 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
829 static void update_help_window(void)
831 update_window(help_win, 0, 0, COLS, "Settings", print_help);
834 static void draw_separator(void)
836 int row;
838 bkgdset(pairs[CURSED_WIN_TITLE]);
839 mvaddch(0, tree_win_w, ' ');
840 bkgdset(pairs[CURSED_SEPARATOR]);
841 for (row = 1; row < LINES - 3; row++)
842 mvaddch(row, tree_win_w, ACS_VLINE);
845 static void do_update_view(int full)
847 cursor_x = -1;
848 cursor_y = -1;
850 switch (cur_view) {
851 case TREE_VIEW:
852 editable_lock();
853 if (full || lib_tree_win->changed)
854 update_tree_window();
855 if (full || lib_track_win->changed)
856 update_track_window();
857 editable_unlock();
858 draw_separator();
859 break;
860 case SORTED_VIEW:
861 editable_lock();
862 update_sorted_window();
863 editable_unlock();
864 break;
865 case PLAYLIST_VIEW:
866 editable_lock();
867 update_pl_window();
868 editable_unlock();
869 break;
870 case QUEUE_VIEW:
871 editable_lock();
872 update_play_queue_window();
873 editable_unlock();
874 break;
875 case BROWSER_VIEW:
876 update_browser_window();
877 break;
878 case FILTERS_VIEW:
879 update_filters_window();
880 break;
881 case HELP_VIEW:
882 update_help_window();
883 break;
887 static void do_update_statusline(void)
889 static const char *status_strs[] = { ".", ">", "|" };
890 static const char *cont_strs[] = { " ", "C" };
891 static const char *repeat_strs[] = { " ", "R" };
892 static const char *shuffle_strs[] = { " ", "S" };
893 int buffer_fill, vol, vol_left, vol_right;
894 int duration = -1;
895 char *msg;
896 char format[80];
898 editable_lock();
899 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
900 pl_editable.total_time, 0);
901 editable_unlock();
903 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
904 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
905 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
907 player_info_lock();
909 if (player_info.ti)
910 duration = player_info.ti->duration;
912 if (soft_vol) {
913 vol_left = soft_vol_l;
914 vol_right = soft_vol_r;
915 vol = (vol_left + vol_right + 1) / 2;
916 } else if (!volume_max) {
917 vol_left = vol_right = vol = -1;
918 } else {
919 vol_left = scale_to_percentage(volume_l, volume_max);
920 vol_right = scale_to_percentage(volume_r, volume_max);
921 vol = (vol_left + vol_right + 1) / 2;
923 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
925 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
927 if (show_remaining_time && duration != -1) {
928 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
929 } else {
930 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
933 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
934 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
935 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
936 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
937 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
938 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
940 strcpy(format, " %s %p ");
941 if (duration != -1)
942 strcat(format, "/ %d ");
943 strcat(format, "- %t ");
944 if (vol >= 0) {
945 if (vol_left != vol_right) {
946 strcat(format, "vol: %l,%r ");
947 } else {
948 strcat(format, "vol: %v ");
951 if (player_info.ti && is_url(player_info.ti->filename))
952 strcat(format, "buf: %b ");
953 strcat(format, "%=");
954 if (player_repeat_current) {
955 strcat(format, "repeat current");
956 } else if (play_library) {
957 /* artist/album modes work only in lib */
958 if (shuffle) {
959 /* shuffle overrides sorted mode */
960 strcat(format, "%L from library");
961 } else if (play_sorted) {
962 strcat(format, "%L from sorted library");
963 } else {
964 strcat(format, "%L from library");
966 } else {
967 strcat(format, "playlist");
969 strcat(format, " | %1C%1R%1S ");
970 format_print(print_buffer, COLS, format, status_fopts);
972 msg = player_info.error_msg;
973 player_info.error_msg = NULL;
975 player_info_unlock();
977 bkgdset(pairs[CURSED_STATUSLINE]);
978 dump_print_buffer(LINES - 2, 0);
980 if (msg) {
981 error_msg("%s", msg);
982 free(msg);
986 static void dump_buffer(const char *buffer)
988 if (using_utf8) {
989 addstr(buffer);
990 } else {
991 utf8_decode(buffer);
992 addstr(conv_buffer);
996 static void do_update_commandline(void)
998 char *str;
999 int w, idx;
1000 char ch;
1002 move(LINES - 1, 0);
1003 if (error_buf[0]) {
1004 if (msg_is_error) {
1005 bkgdset(pairs[CURSED_ERROR]);
1006 } else {
1007 bkgdset(pairs[CURSED_INFO]);
1009 addstr(error_buf);
1010 clrtoeol();
1011 return;
1013 bkgdset(pairs[CURSED_COMMANDLINE]);
1014 if (input_mode == NORMAL_MODE) {
1015 clrtoeol();
1016 return;
1019 str = cmdline.line;
1020 if (!using_utf8) {
1021 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1022 * characters are invalid UTF-8 so it really is in locale's
1023 * encoding.
1025 * This code should be safe because cmdline.bpos ==
1026 * cmdline.cpos as every non-ASCII character is counted as one
1027 * invalid UTF-8 byte.
1029 * NOTE: This has nothing to do with widths of printed
1030 * characters. I.e. even if there were control characters
1031 * (displayed as <xx>) there would be no problem because bpos
1032 * still equals to cpos, I think.
1034 utf8_encode(cmdline.line);
1035 str = conv_buffer;
1038 /* COMMAND_MODE or SEARCH_MODE */
1039 w = u_str_width(str);
1040 ch = ':';
1041 if (input_mode == SEARCH_MODE)
1042 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
1044 if (w <= COLS - 2) {
1045 addch(ch);
1046 idx = u_copy_chars(print_buffer, str, &w);
1047 print_buffer[idx] = 0;
1048 dump_buffer(print_buffer);
1049 clrtoeol();
1050 } else {
1051 /* keep cursor as far right as possible */
1052 int skip, width, cw;
1054 /* cursor pos (width, not chars. doesn't count the ':') */
1055 cw = u_str_nwidth(str, cmdline.cpos);
1057 skip = cw + 2 - COLS;
1058 if (skip > 0) {
1059 /* skip the ':' */
1060 skip--;
1062 /* skip rest (if any) */
1063 idx = u_skip_chars(str, &skip);
1065 width = COLS;
1066 idx = u_copy_chars(print_buffer, str + idx, &width);
1067 while (width < COLS) {
1068 /* cursor is at end of the buffer
1069 * print 1, 2 or 3 spaces
1071 * To clarify:
1073 * If the last _skipped_ character was double-width we may need
1074 * to print 2 spaces.
1076 * If the last _skipped_ character was invalid UTF-8 we may need
1077 * to print 3 spaces.
1079 print_buffer[idx++] = ' ';
1080 width++;
1082 print_buffer[idx] = 0;
1083 dump_buffer(print_buffer);
1084 } else {
1085 /* print ':' + COLS - 1 chars */
1086 addch(ch);
1087 width = COLS - 1;
1088 idx = u_copy_chars(print_buffer, str, &width);
1089 print_buffer[idx] = 0;
1090 dump_buffer(print_buffer);
1095 /* lock player_info! */
1096 static const char *get_stream_title(void)
1098 static char stream_title[255 * 16 + 1];
1099 char *ptr, *title;
1101 ptr = strstr(player_info.metadata, "StreamTitle='");
1102 if (ptr == NULL)
1103 return NULL;
1104 ptr += 13;
1105 title = ptr;
1106 while (*ptr) {
1107 if (*ptr == '\'' && *(ptr + 1) == ';') {
1108 memcpy(stream_title, title, ptr - title);
1109 stream_title[ptr - title] = 0;
1110 return stream_title;
1112 ptr++;
1114 return NULL;
1117 static void set_title(const char *title)
1119 if (!set_term_title)
1120 return;
1122 if (t_ts) {
1123 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1124 fflush(stdout);
1128 static void do_update_titleline(void)
1130 bkgdset(pairs[CURSED_TITLELINE]);
1131 player_info_lock();
1132 if (player_info.ti) {
1133 int i, use_alt_format = 0;
1134 char *wtitle;
1136 fill_track_fopts_track_info(player_info.ti);
1137 if (is_url(player_info.ti->filename)) {
1138 const char *title = get_stream_title();
1140 if (title == NULL)
1141 use_alt_format = 1;
1142 fopt_set_str(&track_fopts[TF_TITLE], title);
1143 } else {
1144 use_alt_format = !track_info_has_tag(player_info.ti);
1147 if (use_alt_format) {
1148 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1149 } else {
1150 format_print(print_buffer, COLS, current_format, track_fopts);
1152 dump_print_buffer(LINES - 3, 0);
1154 /* set window title */
1155 if (use_alt_format) {
1156 format_print(print_buffer, sizeof(print_buffer) - 1,
1157 window_title_alt_format, track_fopts);
1158 } else {
1159 format_print(print_buffer, sizeof(print_buffer) - 1,
1160 window_title_format, track_fopts);
1163 /* remove whitespace */
1164 i = strlen(print_buffer) - 1;
1165 while (i > 0 && print_buffer[i] == ' ')
1166 i--;
1167 print_buffer[i + 1] = 0;
1169 if (using_utf8) {
1170 wtitle = print_buffer;
1171 } else {
1172 utf8_decode(print_buffer);
1173 wtitle = conv_buffer;
1176 set_title(wtitle);
1177 } else {
1178 move(LINES - 3, 0);
1179 clrtoeol();
1181 set_title("cmus " VERSION);
1183 player_info_unlock();
1186 static int cmdline_cursor_column(void)
1188 char *str;
1189 int cw, skip, s;
1191 str = cmdline.line;
1192 if (!using_utf8) {
1193 /* see do_update_commandline */
1194 utf8_encode(cmdline.line);
1195 str = conv_buffer;
1198 /* width of the text in the buffer before cursor */
1199 cw = u_str_nwidth(str, cmdline.cpos);
1201 if (1 + cw < COLS) {
1202 /* whole line is visible */
1203 return 1 + cw;
1206 /* beginning of cmdline is not visible */
1208 /* check if the first visible char in cmdline would be halved
1209 * double-width character (or invalid byte <xx>) which is not possible.
1210 * we need to skip the whole character and move cursor to COLS - 2
1211 * column. */
1212 skip = cw + 2 - COLS;
1214 /* skip the ':' */
1215 skip--;
1217 /* skip rest */
1218 s = skip;
1219 u_skip_chars(str, &s);
1220 if (s > skip) {
1221 /* the last skipped char was double-width or <xx> */
1222 return COLS - 1 - (s - skip);
1224 return COLS - 1;
1227 static void post_update(void)
1229 /* refresh makes cursor visible at least for urxvt */
1230 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1231 move(LINES - 1, cmdline_cursor_column());
1232 refresh();
1233 curs_set(1);
1234 } else {
1235 if (cursor_x >= 0) {
1236 move(cursor_y, cursor_x);
1237 } else {
1238 move(LINES - 1, 0);
1240 refresh();
1241 curs_set(0);
1245 void update_titleline(void)
1247 curs_set(0);
1248 do_update_titleline();
1249 post_update();
1252 void update_full(void)
1254 if (!ui_initialized)
1255 return;
1257 curs_set(0);
1259 do_update_view(1);
1260 do_update_titleline();
1261 do_update_statusline();
1262 do_update_commandline();
1264 post_update();
1267 static void update_commandline(void)
1269 curs_set(0);
1270 do_update_commandline();
1271 post_update();
1274 void update_statusline(void)
1276 if (!ui_initialized)
1277 return;
1279 curs_set(0);
1280 do_update_statusline();
1281 post_update();
1284 void info_msg(const char *format, ...)
1286 va_list ap;
1288 va_start(ap, format);
1289 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1290 va_end(ap);
1292 msg_is_error = 0;
1294 update_commandline();
1297 void error_msg(const char *format, ...)
1299 va_list ap;
1301 strcpy(error_buf, "Error: ");
1302 va_start(ap, format);
1303 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1304 va_end(ap);
1306 d_print("%s\n", error_buf);
1308 msg_is_error = 1;
1309 error_count++;
1311 if (ui_initialized) {
1312 error_time = time(NULL);
1313 update_commandline();
1314 } else {
1315 warn("%s\n", error_buf);
1316 error_buf[0] = 0;
1320 int yes_no_query(const char *format, ...)
1322 char buffer[512];
1323 va_list ap;
1324 int ret = 0;
1326 va_start(ap, format);
1327 vsnprintf(buffer, sizeof(buffer), format, ap);
1328 va_end(ap);
1330 move(LINES - 1, 0);
1331 bkgdset(pairs[CURSED_INFO]);
1333 /* no need to convert buffer.
1334 * it is always encoded in the right charset (assuming filenames are
1335 * encoded in same charset as LC_CTYPE).
1338 addstr(buffer);
1339 clrtoeol();
1340 refresh();
1342 while (1) {
1343 int ch = getch();
1345 if (ch == ERR || ch == 0)
1346 continue;
1347 if (ch == 'y')
1348 ret = 1;
1349 break;
1351 update_commandline();
1352 return ret;
1355 void search_not_found(void)
1357 const char *what = "Track";
1359 if (search_restricted) {
1360 switch (cur_view) {
1361 case TREE_VIEW:
1362 what = "Artist/album";
1363 break;
1364 case SORTED_VIEW:
1365 case PLAYLIST_VIEW:
1366 case QUEUE_VIEW:
1367 what = "Title";
1368 break;
1369 case BROWSER_VIEW:
1370 what = "File/Directory";
1371 break;
1372 case FILTERS_VIEW:
1373 what = "Filter";
1374 break;
1375 case HELP_VIEW:
1376 what = "Binding/command/option";
1377 break;
1379 } else {
1380 switch (cur_view) {
1381 case TREE_VIEW:
1382 case SORTED_VIEW:
1383 case PLAYLIST_VIEW:
1384 case QUEUE_VIEW:
1385 what = "Track";
1386 break;
1387 case BROWSER_VIEW:
1388 what = "File/Directory";
1389 break;
1390 case FILTERS_VIEW:
1391 what = "Filter";
1392 break;
1393 case HELP_VIEW:
1394 what = "Binding/command/option";
1395 break;
1398 info_msg("%s not found: %s", what, search_str ? : "");
1401 void set_view(int view)
1403 if (view == cur_view)
1404 return;
1406 cur_view = view;
1407 switch (cur_view) {
1408 case TREE_VIEW:
1409 searchable = tree_searchable;
1410 break;
1411 case SORTED_VIEW:
1412 searchable = lib_editable.searchable;
1413 break;
1414 case PLAYLIST_VIEW:
1415 searchable = pl_editable.searchable;
1416 break;
1417 case QUEUE_VIEW:
1418 searchable = pq_editable.searchable;
1419 break;
1420 case BROWSER_VIEW:
1421 searchable = browser_searchable;
1422 break;
1423 case FILTERS_VIEW:
1424 searchable = filters_searchable;
1425 break;
1426 case HELP_VIEW:
1427 searchable = help_searchable;
1428 update_help_window();
1429 break;
1432 curs_set(0);
1433 do_update_view(1);
1434 post_update();
1437 void enter_command_mode(void)
1439 error_buf[0] = 0;
1440 error_time = 0;
1441 input_mode = COMMAND_MODE;
1442 update_commandline();
1445 void enter_search_mode(void)
1447 error_buf[0] = 0;
1448 error_time = 0;
1449 input_mode = SEARCH_MODE;
1450 search_direction = SEARCH_FORWARD;
1451 update_commandline();
1454 void enter_search_backward_mode(void)
1456 error_buf[0] = 0;
1457 error_time = 0;
1458 input_mode = SEARCH_MODE;
1459 search_direction = SEARCH_BACKWARD;
1460 update_commandline();
1463 void update_colors(void)
1465 int i;
1467 if (!ui_initialized)
1468 return;
1470 for (i = 0; i < NR_CURSED; i++) {
1471 int bg = colors[cursed_to_bg_idx[i]];
1472 int fg = colors[cursed_to_fg_idx[i]];
1473 int pair = i + 1;
1475 if (fg >= 8 && fg <= 15) {
1476 /* fg colors 8..15 are special (0..7 + bold) */
1477 init_pair(pair, fg & 7, bg);
1478 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1479 } else {
1480 init_pair(pair, fg, bg);
1481 pairs[i] = COLOR_PAIR(pair);
1486 static void clear_error(void)
1488 time_t t = time(NULL);
1490 /* prevent accidental clearing of error messages */
1491 if (t - error_time < 2)
1492 return;
1494 if (error_buf[0]) {
1495 error_time = 0;
1496 error_buf[0] = 0;
1497 update_commandline();
1501 /* screen updates }}} */
1503 static void spawn_status_program(void)
1505 static const char *status_strs[] = { "stopped", "playing", "paused" };
1506 const char *stream_title = NULL;
1507 char *argv[32];
1508 int i, status;
1510 if (status_display_program == NULL || status_display_program[0] == 0)
1511 return;
1513 player_info_lock();
1514 status = player_info.status;
1515 if (status == 1)
1516 stream_title = get_stream_title();
1518 i = 0;
1519 argv[i++] = xstrdup(status_display_program);
1521 argv[i++] = xstrdup("status");
1522 argv[i++] = xstrdup(status_strs[status]);
1523 if (player_info.ti) {
1524 static const char *keys[] = {
1525 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1527 int j;
1529 if (is_url(player_info.ti->filename)) {
1530 argv[i++] = xstrdup("url");
1531 argv[i++] = xstrdup(player_info.ti->filename);
1532 if (stream_title) {
1533 argv[i++] = xstrdup("title");
1534 argv[i++] = xstrdup(stream_title);
1536 } else {
1537 char buf[32];
1539 argv[i++] = xstrdup("file");
1540 argv[i++] = xstrdup(player_info.ti->filename);
1541 for (j = 0; keys[j]; j++) {
1542 const char *key = keys[j];
1543 const char *val;
1545 val = keyvals_get_val(player_info.ti->comments, key);
1546 if (val) {
1547 argv[i++] = xstrdup(key);
1548 argv[i++] = xstrdup(val);
1551 snprintf(buf, sizeof(buf), "%d", player_info.ti->duration);
1552 argv[i++] = xstrdup("duration");
1553 argv[i++] = xstrdup(buf);
1556 argv[i++] = NULL;
1557 player_info_unlock();
1559 if (spawn(argv, &status) == -1)
1560 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1561 for (i = 0; argv[i]; i++)
1562 free(argv[i]);
1565 static int ctrl_c_pressed = 0;
1567 static void sig_int(int sig)
1569 ctrl_c_pressed = 1;
1572 static void sig_hup(int sig)
1574 cmus_running = 0;
1577 static int needs_to_resize = 1;
1579 static void sig_winch(int sig)
1581 needs_to_resize = 1;
1584 static int get_window_size(int *lines, int *columns)
1586 struct winsize ws;
1588 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1589 return -1;
1590 *columns = ws.ws_col;
1591 *lines = ws.ws_row;
1592 return 0;
1595 static void resize_tree_view(int w, int h)
1597 tree_win_w = w / 3;
1598 track_win_w = w - tree_win_w - 1;
1599 if (tree_win_w < 8)
1600 tree_win_w = 8;
1601 if (track_win_w < 8)
1602 track_win_w = 8;
1603 tree_win_x = 0;
1604 tree_win_y = 0;
1605 track_win_x = tree_win_w + 1;
1606 track_win_y = 0;
1608 h--;
1609 window_set_nr_rows(lib_tree_win, h);
1610 window_set_nr_rows(lib_track_win, h);
1613 static void update(void)
1615 int needs_view_update = 0;
1616 int needs_title_update = 0;
1617 int needs_status_update = 0;
1618 int needs_command_update = 0;
1619 int needs_spawn = 0;
1621 if (needs_to_resize) {
1622 int w, h;
1623 int columns, lines;
1625 if (get_window_size(&lines, &columns) == 0) {
1626 needs_to_resize = 0;
1627 resizeterm(lines, columns);
1628 w = COLS;
1629 h = LINES - 3;
1630 if (w < 16)
1631 w = 16;
1632 if (h < 8)
1633 h = 8;
1634 editable_lock();
1635 resize_tree_view(w, h);
1636 window_set_nr_rows(lib_editable.win, h - 1);
1637 window_set_nr_rows(pl_editable.win, h - 1);
1638 window_set_nr_rows(pq_editable.win, h - 1);
1639 window_set_nr_rows(filters_win, h - 1);
1640 window_set_nr_rows(help_win, h - 1);
1641 window_set_nr_rows(browser_win, h - 1);
1642 editable_unlock();
1643 needs_title_update = 1;
1644 needs_status_update = 1;
1645 needs_command_update = 1;
1649 player_info_lock();
1650 editable_lock();
1652 needs_spawn = player_info.status_changed || player_info.file_changed ||
1653 player_info.metadata_changed;
1655 if (player_info.file_changed) {
1656 player_info.file_changed = 0;
1657 needs_title_update = 1;
1658 needs_status_update = 1;
1660 if (player_info.metadata_changed) {
1661 player_info.metadata_changed = 0;
1662 needs_title_update = 1;
1664 if (player_info.position_changed || player_info.status_changed) {
1665 player_info.position_changed = 0;
1666 player_info.status_changed = 0;
1668 needs_status_update = 1;
1670 switch (cur_view) {
1671 case TREE_VIEW:
1672 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
1673 break;
1674 case SORTED_VIEW:
1675 needs_view_update += lib_editable.win->changed;
1676 break;
1677 case PLAYLIST_VIEW:
1678 needs_view_update += pl_editable.win->changed;
1679 break;
1680 case QUEUE_VIEW:
1681 needs_view_update += pq_editable.win->changed;
1682 break;
1683 case BROWSER_VIEW:
1684 needs_view_update += browser_win->changed;
1685 break;
1686 case FILTERS_VIEW:
1687 needs_view_update += filters_win->changed;
1688 break;
1689 case HELP_VIEW:
1690 needs_view_update += help_win->changed;
1691 break;
1694 /* total time changed? */
1695 if (play_library) {
1696 needs_status_update += lib_editable.win->changed;
1697 lib_editable.win->changed = 0;
1698 } else {
1699 needs_status_update += pl_editable.win->changed;
1700 lib_editable.win->changed = 0;
1703 editable_unlock();
1704 player_info_unlock();
1706 if (needs_spawn)
1707 spawn_status_program();
1709 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1710 curs_set(0);
1712 if (needs_view_update)
1713 do_update_view(0);
1714 if (needs_title_update)
1715 do_update_titleline();
1716 if (needs_status_update)
1717 do_update_statusline();
1718 if (needs_command_update)
1719 do_update_commandline();
1720 post_update();
1724 static void handle_ch(uchar ch)
1726 clear_error();
1727 if (input_mode == NORMAL_MODE) {
1728 normal_mode_ch(ch);
1729 } else if (input_mode == COMMAND_MODE) {
1730 command_mode_ch(ch);
1731 update_commandline();
1732 } else if (input_mode == SEARCH_MODE) {
1733 search_mode_ch(ch);
1734 update_commandline();
1738 static void handle_key(int key)
1740 clear_error();
1741 if (input_mode == NORMAL_MODE) {
1742 normal_mode_key(key);
1743 } else if (input_mode == COMMAND_MODE) {
1744 command_mode_key(key);
1745 update_commandline();
1746 } else if (input_mode == SEARCH_MODE) {
1747 search_mode_key(key);
1748 update_commandline();
1752 static void u_getch(void)
1754 int key;
1755 int bit = 7;
1756 int mask = (1 << 7);
1757 uchar u, ch;
1759 key = getch();
1760 if (key == ERR || key == 0)
1761 return;
1763 if (key > 255) {
1764 handle_key(key);
1765 return;
1768 ch = (unsigned char)key;
1769 while (bit > 0 && ch & mask) {
1770 mask >>= 1;
1771 bit--;
1773 if (bit == 7) {
1774 /* ascii */
1775 u = ch;
1776 } else {
1777 int count;
1779 u = ch & ((1 << bit) - 1);
1780 count = 6 - bit;
1781 while (count) {
1782 key = getch();
1783 if (key == ERR || key == 0)
1784 return;
1786 ch = (unsigned char)key;
1787 u = (u << 6) | (ch & 63);
1788 count--;
1791 handle_ch(u);
1794 static void main_loop(void)
1796 int rc, fd_high;
1798 fd_high = server_socket;
1799 while (cmus_running) {
1800 fd_set set;
1801 struct timeval tv;
1802 int poll_mixer = 0;
1803 int i, nr_fds = 0;
1804 int fds[NR_MIXER_FDS];
1805 struct list_head *item;
1806 struct client *client;
1808 update();
1810 /* Timeout must be so small that screen updates seem instant.
1811 * Only affects changes done in other threads (worker, player).
1813 * Too small timeout makes window updates too fast (wastes CPU).
1815 * Too large timeout makes status line (position) updates too slow.
1816 * The timeout is accuracy of player position.
1818 tv.tv_sec = 0;
1819 tv.tv_usec = 0;
1821 player_info_lock();
1822 if (player_info.status == PLAYER_STATUS_PLAYING) {
1823 // player position updates need to be fast
1824 tv.tv_usec = 100e3;
1826 player_info_unlock();
1828 if (!tv.tv_usec && worker_has_job(JOB_TYPE_ANY)) {
1829 // playlist is loading. screen needs to be updated
1830 tv.tv_usec = 250e3;
1833 FD_ZERO(&set);
1834 FD_SET(0, &set);
1835 FD_SET(server_socket, &set);
1836 list_for_each_entry(client, &client_head, node) {
1837 FD_SET(client->fd, &set);
1838 if (client->fd > fd_high)
1839 fd_high = client->fd;
1841 if (!soft_vol) {
1842 nr_fds = mixer_get_fds(fds);
1843 if (nr_fds == -OP_ERROR_NOT_SUPPORTED) {
1844 // mixer has no pollable file descriptors
1845 poll_mixer = 1;
1846 if (!tv.tv_usec)
1847 tv.tv_usec = 500e3;
1849 for (i = 0; i < nr_fds; i++) {
1850 BUG_ON(fds[i] <= 0);
1851 FD_SET(fds[i], &set);
1852 if (fds[i] > fd_high)
1853 fd_high = fds[i];
1857 if (tv.tv_usec) {
1858 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1859 } else {
1860 rc = select(fd_high + 1, &set, NULL, NULL, NULL);
1862 if (poll_mixer) {
1863 int ol = volume_l;
1864 int or = volume_r;
1866 mixer_read_volume();
1867 if (ol != volume_l || or != volume_r)
1868 update_statusline();
1871 if (rc <= 0) {
1872 if (ctrl_c_pressed) {
1873 handle_ch(0x03);
1874 ctrl_c_pressed = 0;
1877 continue;
1880 for (i = 0; i < nr_fds; i++) {
1881 if (FD_ISSET(fds[i], &set)) {
1882 d_print("vol changed\n");
1883 mixer_read_volume();
1884 update_statusline();
1887 if (FD_ISSET(server_socket, &set))
1888 server_accept();
1890 // server_serve() can remove client from the list
1891 item = client_head.next;
1892 while (item != &client_head) {
1893 struct list_head *next = item->next;
1894 client = container_of(item, struct client, node);
1895 if (FD_ISSET(client->fd, &set))
1896 server_serve(client);
1897 item = next;
1900 if (FD_ISSET(0, &set)) {
1901 if (using_utf8) {
1902 u_getch();
1903 } else {
1904 int key = getch();
1906 if (key != ERR && key != 0) {
1907 if (key > 255) {
1908 handle_key(key);
1909 } else {
1910 uchar ch = key;
1912 if (ch > 0x7f)
1913 ch |= U_INVALID_MASK;
1914 handle_ch(ch);
1922 static int get_next(struct track_info **ti)
1924 struct track_info *info;
1926 editable_lock();
1927 info = play_queue_remove();
1928 if (info == NULL) {
1929 if (play_library) {
1930 info = lib_set_next();
1931 } else {
1932 info = pl_set_next();
1935 editable_unlock();
1937 if (info == NULL)
1938 return -1;
1940 *ti = info;
1941 return 0;
1944 static const struct player_callbacks player_callbacks = {
1945 .get_next = get_next
1948 static void init_curses(void)
1950 struct sigaction act;
1951 char *ptr, *term;
1953 sigemptyset(&act.sa_mask);
1954 act.sa_flags = 0;
1955 act.sa_handler = sig_int;
1956 sigaction(SIGINT, &act, NULL);
1958 sigemptyset(&act.sa_mask);
1959 act.sa_flags = 0;
1960 act.sa_handler = sig_hup;
1961 sigaction(SIGHUP, &act, NULL);
1963 sigemptyset(&act.sa_mask);
1964 act.sa_flags = 0;
1965 act.sa_handler = SIG_IGN;
1966 sigaction(SIGPIPE, &act, NULL);
1968 sigemptyset(&act.sa_mask);
1969 act.sa_flags = 0;
1970 act.sa_handler = sig_winch;
1971 sigaction(SIGWINCH, &act, NULL);
1973 initscr();
1975 /* turn off kb buffering */
1976 cbreak();
1978 keypad(stdscr, TRUE);
1980 /* wait max 5 * 0.1 s if there are no keys available
1981 * doesn't really matter because we use select()
1983 halfdelay(5);
1985 noecho();
1986 if (has_colors()) {
1987 start_color();
1988 use_default_colors();
1990 d_print("Number of supported colors: %d\n", COLORS);
1991 ui_initialized = 1;
1993 /* this was disabled while initializing because it needs to be
1994 * called only once after all colors have been set
1996 update_colors();
1998 ptr = print_buffer;
1999 t_ts = tgetstr("ts", &ptr);
2000 t_fs = tgetstr("fs", &ptr);
2001 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
2003 if (!t_fs)
2004 t_ts = NULL;
2006 term = getenv("TERM");
2007 if (!t_ts && term) {
2009 * Eterm: Eterm
2010 * aterm: rxvt
2011 * mlterm: xterm
2012 * terminal (xfce): xterm
2013 * urxvt: rxvt-unicode
2014 * xterm: xterm, xterm-{,16,88,256}color
2016 if (!strcmp(term, "screen")) {
2017 t_ts = "\033_";
2018 t_fs = "\033\\";
2019 } else if (!strncmp(term, "xterm", 5) ||
2020 !strncmp(term, "rxvt", 4) ||
2021 !strcmp(term, "Eterm")) {
2022 /* \033]1; change icon
2023 * \033]2; change title
2024 * \033]0; change both
2026 t_ts = "\033]0;";
2027 t_fs = "\007";
2032 static void init_all(void)
2034 server_init(server_address);
2036 /* does not select output plugin */
2037 player_init(&player_callbacks);
2039 /* plugins have been loaded so we know what plugin options are available */
2040 options_add();
2042 lib_init();
2043 searchable = tree_searchable;
2044 pl_init();
2045 cmus_init();
2046 browser_init();
2047 filters_init();
2048 help_init();
2049 cmdline_init();
2050 commands_init();
2051 search_mode_init();
2053 /* almost everything must be initialized now */
2054 options_load();
2056 /* finally we can set the output plugin */
2057 player_set_op(output_plugin);
2058 if (!soft_vol)
2059 mixer_open();
2061 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
2062 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
2063 pl_filename = xstrdup(pl_autosave_filename);
2064 lib_filename = xstrdup(lib_autosave_filename);
2066 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
2067 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
2069 if (error_count) {
2070 char buf[16];
2072 warn("Press <enter> to continue.");
2073 fgets(buf, sizeof(buf), stdin);
2075 help_add_all_unbound();
2077 init_curses();
2080 static void exit_all(void)
2082 endwin();
2084 options_exit();
2086 server_exit();
2087 cmus_exit();
2088 cmus_save(lib_for_each, lib_autosave_filename);
2089 cmus_save(pl_for_each, pl_autosave_filename);
2091 player_exit();
2092 op_exit_plugins();
2093 commands_exit();
2094 search_mode_exit();
2095 filters_exit();
2096 help_exit();
2097 browser_exit();
2100 enum {
2101 FLAG_LISTEN,
2102 FLAG_PLUGINS,
2103 FLAG_HELP,
2104 FLAG_VERSION,
2105 NR_FLAGS
2108 static struct option options[NR_FLAGS + 1] = {
2109 { 0, "listen", 1 },
2110 { 0, "plugins", 0 },
2111 { 0, "help", 0 },
2112 { 0, "version", 0 },
2113 { 0, NULL, 0 }
2116 static const char *usage =
2117 "Usage: %s [OPTION]...\n"
2118 "Curses based music player.\n"
2119 "\n"
2120 " --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
2121 " ADDR is either a UNIX socket or host[:port]\n"
2122 " WARNING: using TCP/IP is insecure!\n"
2123 " --plugins list available plugins and exit\n"
2124 " --help display this help and exit\n"
2125 " --version " VERSION "\n"
2126 "\n"
2127 "Use cmus-remote to control cmus from command line.\n"
2128 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2130 int main(int argc, char *argv[])
2132 int list_plugins = 0;
2134 program_name = argv[0];
2135 argv++;
2136 while (1) {
2137 int idx;
2138 char *arg;
2140 idx = get_option(&argv, options, &arg);
2141 if (idx < 0)
2142 break;
2144 switch (idx) {
2145 case FLAG_HELP:
2146 printf(usage, program_name);
2147 return 0;
2148 case FLAG_VERSION:
2149 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
2150 return 0;
2151 case FLAG_PLUGINS:
2152 list_plugins = 1;
2153 break;
2154 case FLAG_LISTEN:
2155 server_address = xstrdup(arg);
2156 break;
2160 setlocale(LC_CTYPE, "");
2161 #ifdef CODESET
2162 charset = nl_langinfo(CODESET);
2163 #else
2164 charset = "ISO-8859-1";
2165 #endif
2166 if (strcmp(charset, "UTF-8") == 0) {
2167 using_utf8 = 1;
2168 } else {
2169 using_utf8 = 0;
2171 misc_init();
2172 if (server_address == NULL)
2173 server_address = xstrjoin(cmus_config_dir, "/socket");
2174 debug_init();
2175 d_print("charset = '%s'\n", charset);
2177 ip_load_plugins();
2178 op_load_plugins();
2179 if (list_plugins) {
2180 ip_dump_plugins();
2181 op_dump_plugins();
2182 return 0;
2184 init_all();
2185 main_loop();
2186 exit_all();
2187 return 0;