build system: Mark clean and distclean .NOTPARALLEL
[cmus.git] / ui_curses.c
blob8d4febe0ab9a286921bc5b31446478bfede4b7a6
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__)
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 /* currently playing file */
87 static struct track_info *cur_track_info = NULL;
89 static int running = 1;
90 static char *lib_autosave_filename;
91 static char *pl_autosave_filename;
93 /* shown error message and time stamp
94 * error is cleared if it is older than 3s and key was pressed
96 static char error_buf[512];
97 static time_t error_time = 0;
98 /* info messages are displayed in different color */
99 static int msg_is_error;
100 static int error_count = 0;
102 static char *server_address = NULL;
104 static char *charset = NULL;
105 static char print_buffer[512];
107 /* destination buffer for utf8_encode and utf8_decode */
108 static char conv_buffer[512];
110 #define print_buffer_size (sizeof(print_buffer) - 1)
111 static int using_utf8;
113 static const char *t_ts;
114 static const char *t_fs;
116 static int tree_win_x = 0;
117 static int tree_win_y = 0;
118 static int tree_win_w = 0;
120 static int track_win_x = 0;
121 static int track_win_y = 0;
122 static int track_win_w = 0;
124 static int cursor_x;
125 static int cursor_y;
127 enum {
128 CURSED_WIN,
129 CURSED_WIN_CUR,
130 CURSED_WIN_SEL,
131 CURSED_WIN_SEL_CUR,
133 CURSED_WIN_ACTIVE,
134 CURSED_WIN_ACTIVE_CUR,
135 CURSED_WIN_ACTIVE_SEL,
136 CURSED_WIN_ACTIVE_SEL_CUR,
138 CURSED_SEPARATOR,
139 CURSED_WIN_TITLE,
140 CURSED_COMMANDLINE,
141 CURSED_STATUSLINE,
143 CURSED_TITLELINE,
144 CURSED_DIR,
145 CURSED_ERROR,
146 CURSED_INFO,
148 NR_CURSED
151 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
152 COLOR_WIN_BG,
153 COLOR_WIN_BG,
154 COLOR_WIN_INACTIVE_SEL_BG,
155 COLOR_WIN_INACTIVE_CUR_SEL_BG,
157 COLOR_WIN_BG,
158 COLOR_WIN_BG,
159 COLOR_WIN_SEL_BG,
160 COLOR_WIN_CUR_SEL_BG,
162 COLOR_WIN_BG,
163 COLOR_WIN_TITLE_BG,
164 COLOR_CMDLINE_BG,
165 COLOR_STATUSLINE_BG,
167 COLOR_TITLELINE_BG,
168 COLOR_WIN_BG,
169 COLOR_CMDLINE_BG,
170 COLOR_CMDLINE_BG
173 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
174 COLOR_WIN_FG,
175 COLOR_WIN_CUR,
176 COLOR_WIN_INACTIVE_SEL_FG,
177 COLOR_WIN_INACTIVE_CUR_SEL_FG,
179 COLOR_WIN_FG,
180 COLOR_WIN_CUR,
181 COLOR_WIN_SEL_FG,
182 COLOR_WIN_CUR_SEL_FG,
184 COLOR_SEPARATOR,
185 COLOR_WIN_TITLE_FG,
186 COLOR_CMDLINE_FG,
187 COLOR_STATUSLINE_FG,
189 COLOR_TITLELINE_FG,
190 COLOR_WIN_DIR,
191 COLOR_ERROR,
192 COLOR_INFO
195 /* index is CURSED_*, value is fucking color pair */
196 static int pairs[NR_CURSED];
198 enum {
199 TF_ARTIST,
200 TF_ALBUM,
201 TF_DISC,
202 TF_TRACK,
203 TF_TITLE,
204 TF_YEAR,
205 TF_GENRE,
206 TF_DURATION,
207 TF_PATHFILE,
208 TF_FILE,
209 NR_TFS
212 static struct format_option track_fopts[NR_TFS + 1] = {
213 DEF_FO_STR('a'),
214 DEF_FO_STR('l'),
215 DEF_FO_INT('D'),
216 DEF_FO_INT('n'),
217 DEF_FO_STR('t'),
218 DEF_FO_STR('y'),
219 DEF_FO_STR('g'),
220 DEF_FO_TIME('d'),
221 DEF_FO_STR('f'),
222 DEF_FO_STR('F'),
223 DEF_FO_END
226 enum {
227 SF_STATUS,
228 SF_POSITION,
229 SF_DURATION,
230 SF_TOTAL,
231 SF_VOLUME,
232 SF_LVOLUME,
233 SF_RVOLUME,
234 SF_BUFFER,
235 SF_REPEAT,
236 SF_CONTINUE,
237 SF_SHUFFLE,
238 SF_PLAYLISTMODE,
239 NR_SFS
242 static struct format_option status_fopts[NR_SFS + 1] = {
243 DEF_FO_STR('s'),
244 DEF_FO_TIME('p'),
245 DEF_FO_TIME('d'),
246 DEF_FO_TIME('t'),
247 DEF_FO_INT('v'),
248 DEF_FO_INT('l'),
249 DEF_FO_INT('r'),
250 DEF_FO_INT('b'),
251 DEF_FO_STR('R'),
252 DEF_FO_STR('C'),
253 DEF_FO_STR('S'),
254 DEF_FO_STR('L'),
255 DEF_FO_END
258 static void utf8_encode(const char *buffer)
260 static iconv_t cd = (iconv_t)-1;
261 size_t is, os;
262 const char *i;
263 char *o;
264 int rc;
266 if (cd == (iconv_t)-1) {
267 d_print("iconv_open(UTF-8, %s)\n", charset);
268 cd = iconv_open("UTF-8", charset);
269 if (cd == (iconv_t)-1) {
270 d_print("iconv_open failed: %s\n", strerror(errno));
271 return;
274 i = buffer;
275 o = conv_buffer;
276 is = strlen(i);
277 os = sizeof(conv_buffer) - 1;
278 rc = iconv(cd, (void *)&i, &is, &o, &os);
279 *o = 0;
280 if (rc == -1) {
281 d_print("iconv failed: %s\n", strerror(errno));
282 return;
286 static void utf8_decode(const char *buffer)
288 static iconv_t cd = (iconv_t)-1;
289 size_t is, os;
290 const char *i;
291 char *o;
292 int rc;
294 if (cd == (iconv_t)-1) {
295 d_print("iconv_open(%s, UTF-8)\n", charset);
296 cd = iconv_open(charset, "UTF-8");
297 if (cd == (iconv_t)-1) {
298 d_print("iconv_open failed: %s\n", strerror(errno));
299 return;
302 i = buffer;
303 o = conv_buffer;
304 is = strlen(i);
305 os = sizeof(conv_buffer) - 1;
306 rc = iconv(cd, (void *)&i, &is, &o, &os);
307 *o = 0;
308 if (rc == -1) {
309 d_print("iconv failed: %s\n", strerror(errno));
310 return;
314 /* screen updates {{{ */
316 static void dump_print_buffer(int row, int col)
318 if (using_utf8) {
319 mvaddstr(row, col, print_buffer);
320 } else {
321 utf8_decode(print_buffer);
322 mvaddstr(row, col, conv_buffer);
326 /* print @str into @buf
328 * if @str is shorter than @width pad with spaces
329 * if @str is wider than @width truncate and add "..."
331 static int format_str(char *buf, const char *str, int width)
333 int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;
335 while (1) {
336 uchar u;
337 int w;
339 u_get_char(str, &s, &u);
340 if (u == 0) {
341 memset(buf + d, ' ', width);
342 d += width;
343 break;
346 w = u_char_width(u);
347 if (width == 3)
348 ellipsis_pos = d;
349 if (width == 4 && w == 2) {
350 /* can't cut double-width char */
351 ellipsis_pos = d + 1;
352 cut_double_width = 1;
355 width -= w;
356 if (width < 0) {
357 /* does not fit */
358 d = ellipsis_pos;
359 if (cut_double_width) {
360 /* first half of the double-width char */
361 buf[d - 1] = ' ';
363 buf[d++] = '.';
364 buf[d++] = '.';
365 buf[d++] = '.';
366 break;
368 u_set_char(buf, &d, u);
370 return d;
373 static void sprint(int row, int col, const char *str, int width)
375 int pos = 0;
377 print_buffer[pos++] = ' ';
378 pos += format_str(print_buffer + pos, str, width - 2);
379 print_buffer[pos++] = ' ';
380 print_buffer[pos] = 0;
381 dump_print_buffer(row, col);
384 static void sprint_ascii(int row, int col, const char *str, int len)
386 int l;
388 l = strlen(str);
389 len -= 2;
391 print_buffer[0] = ' ';
392 if (l > len) {
393 memcpy(print_buffer + 1, str, len - 3);
394 print_buffer[len - 2] = '.';
395 print_buffer[len - 1] = '.';
396 print_buffer[len - 0] = '.';
397 } else {
398 memcpy(print_buffer + 1, str, l);
399 memset(print_buffer + 1 + l, ' ', len - l);
401 print_buffer[len + 1] = ' ';
402 print_buffer[len + 2] = 0;
403 mvaddstr(row, col, print_buffer);
406 static void print_tree(struct window *win, int row, struct iter *iter)
408 const char *str;
409 struct artist *artist;
410 struct album *album;
411 struct iter sel;
412 int current, selected, active, pos;
414 artist = iter_to_artist(iter);
415 album = iter_to_album(iter);
416 current = 0;
417 if (lib_cur_track) {
418 if (album) {
419 current = CUR_ALBUM == album;
420 } else {
421 current = CUR_ARTIST == artist;
424 window_get_sel(win, &sel);
425 selected = iters_equal(iter, &sel);
426 active = lib_cur_win == lib_tree_win;
427 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
429 if (active && selected) {
430 cursor_x = 0;
431 cursor_y = 1 + row;
434 pos = 0;
435 print_buffer[pos++] = ' ';
436 str = artist->name;
437 if (album) {
438 print_buffer[pos++] = ' ';
439 print_buffer[pos++] = ' ';
440 str = album->name;
442 pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
443 print_buffer[pos++] = ' ';
444 print_buffer[pos++] = 0;
445 dump_print_buffer(tree_win_y + row + 1, tree_win_x);
448 static inline void fopt_set_str(struct format_option *fopt, const char *str)
450 BUG_ON(fopt->type != FO_STR);
451 if (str) {
452 fopt->fo_str = str;
453 fopt->empty = 0;
454 } else {
455 fopt->empty = 1;
459 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
461 BUG_ON(fopt->type != FO_INT);
462 fopt->fo_int = value;
463 fopt->empty = empty;
466 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
468 BUG_ON(fopt->type != FO_TIME);
469 fopt->fo_time = value;
470 fopt->empty = empty;
473 static void fill_track_fopts(struct tree_track *track)
475 const char *filename;
476 const struct track_info *ti = tree_track_info(track);
477 int num, disc;
479 if (using_utf8) {
480 filename = ti->filename;
481 } else {
482 utf8_encode(ti->filename);
483 filename = conv_buffer;
485 disc = track->shuffle_track.simple_track.disc;
486 num = track->shuffle_track.simple_track.num;
488 fopt_set_str(&track_fopts[TF_ARTIST], track->album->artist->name);
489 fopt_set_str(&track_fopts[TF_ALBUM], track->album->name);
490 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
491 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
492 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(ti->comments, "title"));
493 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(ti->comments, "date"));
494 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(ti->comments, "genre"));
495 fopt_set_time(&track_fopts[TF_DURATION], ti->duration, ti->duration == -1);
496 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
497 if (is_url(ti->filename)) {
498 fopt_set_str(&track_fopts[TF_FILE], filename);
499 } else {
500 const char *f;
502 f = strrchr(filename, '/');
503 if (f) {
504 fopt_set_str(&track_fopts[TF_FILE], f + 1);
505 } else {
506 fopt_set_str(&track_fopts[TF_FILE], filename);
511 static void fill_track_fopts_track_info(struct track_info *info)
513 char *filename;
514 int num, disc;
516 if (using_utf8) {
517 filename = info->filename;
518 } else {
519 utf8_encode(info->filename);
520 filename = conv_buffer;
522 disc = comments_get_int(info->comments, "discnumber");
523 num = comments_get_int(info->comments, "tracknumber");
525 fopt_set_str(&track_fopts[TF_ARTIST], comments_get_val(info->comments, "artist"));
526 fopt_set_str(&track_fopts[TF_ALBUM], comments_get_val(info->comments, "album"));
527 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
528 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
529 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(info->comments, "title"));
530 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(info->comments, "date"));
531 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(info->comments, "genre"));
532 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
533 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
534 if (is_url(info->filename)) {
535 fopt_set_str(&track_fopts[TF_FILE], filename);
536 } else {
537 const char *f;
539 f = strrchr(filename, '/');
540 if (f) {
541 fopt_set_str(&track_fopts[TF_FILE], f + 1);
542 } else {
543 fopt_set_str(&track_fopts[TF_FILE], filename);
548 static void print_track(struct window *win, int row, struct iter *iter)
550 struct tree_track *track;
551 struct iter sel;
552 int current, selected, active;
554 track = iter_to_tree_track(iter);
555 current = lib_cur_track == track;
556 window_get_sel(win, &sel);
557 selected = iters_equal(iter, &sel);
558 active = lib_cur_win == lib_track_win;
559 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
561 if (active && selected) {
562 cursor_x = track_win_x;
563 cursor_y = 1 + row;
566 fill_track_fopts(track);
568 if (track_info_has_tag(tree_track_info(track))) {
569 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
570 } else {
571 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
573 dump_print_buffer(track_win_y + row + 1, track_win_x);
576 /* used by print_editable only */
577 static struct simple_track *current_track;
579 static void print_editable(struct window *win, int row, struct iter *iter)
581 struct simple_track *track;
582 struct iter sel;
583 int current, selected, active;
585 track = iter_to_simple_track(iter);
586 current = current_track == track;
587 window_get_sel(win, &sel);
588 selected = iters_equal(iter, &sel);
590 if (selected) {
591 cursor_x = 0;
592 cursor_y = 1 + row;
595 active = 1;
596 if (!selected && track->marked) {
597 selected = 1;
598 active = 0;
601 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
603 fill_track_fopts_track_info(track->info);
605 if (track_info_has_tag(track->info)) {
606 format_print(print_buffer, COLS, list_win_format, track_fopts);
607 } else {
608 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
610 dump_print_buffer(row + 1, 0);
613 static void print_browser(struct window *win, int row, struct iter *iter)
615 struct browser_entry *e;
616 struct iter sel;
617 int selected;
619 e = iter_to_browser_entry(iter);
620 window_get_sel(win, &sel);
621 selected = iters_equal(iter, &sel);
622 if (selected) {
623 int active = 1;
624 int current = 0;
626 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
627 } else {
628 if (e->type == BROWSER_ENTRY_DIR) {
629 bkgdset(pairs[CURSED_DIR]);
630 } else {
631 bkgdset(pairs[CURSED_WIN]);
635 if (selected) {
636 cursor_x = 0;
637 cursor_y = 1 + row;
640 /* file name encoding == terminal encoding. no need to convert */
641 if (using_utf8) {
642 sprint(row + 1, 0, e->name, COLS);
643 } else {
644 sprint_ascii(row + 1, 0, e->name, COLS);
648 static void print_filter(struct window *win, int row, struct iter *iter)
650 char buf[256];
651 struct filter_entry *e = iter_to_filter_entry(iter);
652 struct iter sel;
653 /* window active? */
654 int active = 1;
655 /* row selected? */
656 int selected;
657 /* is the filter currently active? */
658 int current = !!e->act_stat;
659 const char stat_chars[3] = " *!";
660 int ch1, ch2, ch3, pos;
662 window_get_sel(win, &sel);
663 selected = iters_equal(iter, &sel);
664 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
666 if (selected) {
667 cursor_x = 0;
668 cursor_y = 1 + row;
671 ch1 = ' ';
672 ch3 = ' ';
673 if (e->sel_stat != e->act_stat) {
674 ch1 = '[';
675 ch3 = ']';
677 ch2 = stat_chars[e->sel_stat];
678 snprintf(buf, sizeof(buf), "%c%c%c%-15s %s", ch1, ch2, ch3, e->name, e->filter);
679 pos = format_str(print_buffer, buf, COLS - 1);
680 print_buffer[pos++] = ' ';
681 print_buffer[pos] = 0;
682 dump_print_buffer(row + 1, 0);
685 static void print_help(struct window *win, int row, struct iter *iter)
687 struct iter sel;
688 int selected;
689 int pos;
690 char buf[512];
691 const struct help_entry *e = iter_to_help_entry(iter);
692 const struct cmus_opt *opt;
694 window_get_sel(win, &sel);
695 selected = iters_equal(iter, &sel);
696 bkgdset(pairs[selected << 1]);
698 if (selected) {
699 cursor_x = 0;
700 cursor_y = 1 + row;
703 switch (e->type) {
704 case HE_TEXT:
705 snprintf(buf, sizeof(buf), " %s", e->text);
706 break;
707 case HE_BOUND:
708 snprintf(buf, sizeof(buf), " %-8s %-14s %s",
709 key_context_names[e->binding->ctx],
710 e->binding->key->name,
711 e->binding->cmd);
712 break;
713 case HE_UNBOUND:
714 snprintf(buf, sizeof(buf), " %s", e->command->name);
715 break;
716 case HE_OPTION:
717 opt = e->option;
718 snprintf(buf, sizeof(buf), " %-29s ", opt->name);
719 opt->get(opt->id, buf + strlen(buf));
720 break;
722 pos = format_str(print_buffer, buf, COLS - 1);
723 print_buffer[pos++] = ' ';
724 print_buffer[pos] = 0;
725 dump_print_buffer(row + 1, 0);
728 static void update_window(struct window *win, int x, int y, int w, const char *title,
729 void (*print)(struct window *, int, struct iter *))
731 struct iter iter;
732 int nr_rows;
733 int c, i;
735 win->changed = 0;
737 bkgdset(pairs[CURSED_WIN_TITLE]);
738 c = snprintf(print_buffer, w + 1, " %s", title);
739 if (c > w)
740 c = w;
741 memset(print_buffer + c, ' ', w - c + 1);
742 print_buffer[w] = 0;
743 dump_print_buffer(y, x);
744 nr_rows = window_get_nr_rows(win);
745 i = 0;
746 if (window_get_top(win, &iter)) {
747 while (i < nr_rows) {
748 print(win, i, &iter);
749 i++;
750 if (!window_get_next(win, &iter))
751 break;
755 bkgdset(pairs[0]);
756 memset(print_buffer, ' ', w);
757 print_buffer[w] = 0;
758 while (i < nr_rows) {
759 dump_print_buffer(y + i + 1, x);
760 i++;
764 static void update_tree_window(void)
766 update_window(lib_tree_win, tree_win_x, tree_win_y,
767 tree_win_w, "Artist / Album", print_tree);
770 static void update_track_window(void)
772 char title[512];
774 /* it doesn't matter what format options we use because the format
775 * string does not contain any format charaters */
776 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
777 update_window(lib_track_win, track_win_x, track_win_y,
778 track_win_w, title, print_track);
781 static const char *pretty(const char *path)
783 static int home_len = -1;
784 static char buf[256];
786 if (home_len == -1)
787 home_len = strlen(home_dir);
789 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
790 return path;
792 buf[0] = '~';
793 strcpy(buf + 1, path + home_len);
794 return buf;
797 static const char * const sorted_names[2] = { "", "sorted by " };
799 static void update_editable_window(struct editable *e, const char *title, const char *filename)
801 char buf[512];
802 int pos;
804 if (filename) {
805 if (using_utf8) {
806 /* already UTF-8 */
807 } else {
808 utf8_encode(filename);
809 filename = conv_buffer;
811 snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
812 pretty(filename), e->nr_tracks);
813 } else {
814 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
817 if (e->nr_marked) {
818 pos = strlen(buf);
819 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
821 pos = strlen(buf);
822 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
823 sorted_names[e->sort_str[0] != 0], e->sort_str);
825 update_window(e->win, 0, 0, COLS, buf, &print_editable);
828 static void update_sorted_window(void)
830 current_track = (struct simple_track *)lib_cur_track;
831 update_editable_window(&lib_editable, "Library", lib_filename);
834 static void update_pl_window(void)
836 current_track = pl_cur_track;
837 update_editable_window(&pl_editable, "Playlist", pl_filename);
840 static void update_play_queue_window(void)
842 current_track = NULL;
843 update_editable_window(&pq_editable, "Play Queue", NULL);
846 static void update_browser_window(void)
848 char title[512];
849 char *dirname;
851 if (using_utf8) {
852 /* already UTF-8 */
853 dirname = browser_dir;
854 } else {
855 utf8_encode(browser_dir);
856 dirname = conv_buffer;
858 snprintf(title, sizeof(title), "Browser - %s", dirname);
859 update_window(browser_win, 0, 0, COLS, title, print_browser);
862 static void update_filters_window(void)
864 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
867 static void update_help_window(void)
869 update_window(help_win, 0, 0, COLS, "Settings", print_help);
872 static void draw_separator(void)
874 int row;
876 bkgdset(pairs[CURSED_WIN_TITLE]);
877 mvaddch(0, tree_win_w, ' ');
878 bkgdset(pairs[CURSED_SEPARATOR]);
879 for (row = 1; row < LINES - 3; row++)
880 mvaddch(row, tree_win_w, ACS_VLINE);
883 static void do_update_view(int full)
885 cursor_x = -1;
886 cursor_y = -1;
888 switch (cur_view) {
889 case TREE_VIEW:
890 editable_lock();
891 if (full || lib_tree_win->changed)
892 update_tree_window();
893 if (full || lib_track_win->changed)
894 update_track_window();
895 editable_unlock();
896 draw_separator();
897 break;
898 case SORTED_VIEW:
899 editable_lock();
900 update_sorted_window();
901 editable_unlock();
902 break;
903 case PLAYLIST_VIEW:
904 editable_lock();
905 update_pl_window();
906 editable_unlock();
907 break;
908 case QUEUE_VIEW:
909 editable_lock();
910 update_play_queue_window();
911 editable_unlock();
912 break;
913 case BROWSER_VIEW:
914 update_browser_window();
915 break;
916 case FILTERS_VIEW:
917 update_filters_window();
918 break;
919 case HELP_VIEW:
920 update_help_window();
921 break;
925 static void do_update_statusline(void)
927 static const char *status_strs[] = { ".", ">", "|" };
928 static const char *cont_strs[] = { " ", "C" };
929 static const char *repeat_strs[] = { " ", "R" };
930 static const char *shuffle_strs[] = { " ", "S" };
931 int buffer_fill, vol, vol_left, vol_right;
932 int duration = -1;
933 char *msg;
934 char format[80];
936 editable_lock();
937 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
938 pl_editable.total_time, 0);
939 editable_unlock();
941 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
942 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
943 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
945 if (cur_track_info)
946 duration = cur_track_info->duration;
948 player_info_lock();
950 if (volume_max == 0) {
951 vol_left = vol_right = vol = -1;
952 } else {
953 vol_left = scale_to_percentage(player_info.vol_left, volume_max);
954 vol_right = scale_to_percentage(player_info.vol_right, volume_max);
955 vol = (vol_left + vol_right + 1) / 2;
957 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
959 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
961 if (show_remaining_time && duration != -1) {
962 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
963 } else {
964 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
967 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
968 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
969 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
970 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
971 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
972 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
974 strcpy(format, " %s %p ");
975 if (duration != -1)
976 strcat(format, "/ %d ");
977 strcat(format, "- %t ");
978 if (volume_max != 0) {
979 if (player_info.vol_left != player_info.vol_right) {
980 strcat(format, "vol: %l,%r ");
981 } else {
982 strcat(format, "vol: %v ");
985 if (cur_track_info && is_url(cur_track_info->filename))
986 strcat(format, "buf: %b ");
987 strcat(format, "%=");
988 if (play_library) {
989 /* artist/album modes work only in lib */
990 if (shuffle) {
991 /* shuffle overrides sorted mode */
992 strcat(format, "%L from library");
993 } else if (play_sorted) {
994 strcat(format, "%L from sorted library");
995 } else {
996 strcat(format, "%L from library");
998 } else {
999 strcat(format, "playlist");
1001 strcat(format, " | %1C%1R%1S ");
1002 format_print(print_buffer, COLS, format, status_fopts);
1004 msg = player_info.error_msg;
1005 player_info.error_msg = NULL;
1007 player_info_unlock();
1009 bkgdset(pairs[CURSED_STATUSLINE]);
1010 dump_print_buffer(LINES - 2, 0);
1012 if (msg) {
1013 error_msg("%s", msg);
1014 free(msg);
1018 static void dump_buffer(const char *buffer)
1020 if (using_utf8) {
1021 addstr(buffer);
1022 } else {
1023 utf8_decode(buffer);
1024 addstr(conv_buffer);
1028 static void do_update_commandline(void)
1030 char *str;
1031 int w, idx;
1032 char ch;
1034 move(LINES - 1, 0);
1035 if (error_buf[0]) {
1036 if (msg_is_error) {
1037 bkgdset(pairs[CURSED_ERROR]);
1038 } else {
1039 bkgdset(pairs[CURSED_INFO]);
1041 addstr(error_buf);
1042 clrtoeol();
1043 return;
1045 bkgdset(pairs[CURSED_COMMANDLINE]);
1046 if (input_mode == NORMAL_MODE) {
1047 clrtoeol();
1048 return;
1051 str = cmdline.line;
1052 if (!using_utf8) {
1053 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1054 * characters are invalid UTF-8 so it really is in locale's
1055 * encoding.
1057 * This code should be safe because cmdline.bpos ==
1058 * cmdline.cpos as every non-ASCII character is counted as one
1059 * invalid UTF-8 byte.
1061 * NOTE: This has nothing to do with widths of printed
1062 * characters. I.e. even if there were control characters
1063 * (displayed as <xx>) there would be no problem because bpos
1064 * still equals to cpos, I think.
1066 utf8_encode(cmdline.line);
1067 str = conv_buffer;
1070 /* COMMAND_MODE or SEARCH_MODE */
1071 w = u_str_width(str);
1072 ch = ':';
1073 if (input_mode == SEARCH_MODE)
1074 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
1076 if (w <= COLS - 2) {
1077 addch(ch);
1078 idx = u_copy_chars(print_buffer, str, &w);
1079 print_buffer[idx] = 0;
1080 dump_buffer(print_buffer);
1081 clrtoeol();
1082 } else {
1083 /* keep cursor as far right as possible */
1084 int skip, width, cw;
1086 /* cursor pos (width, not chars. doesn't count the ':') */
1087 cw = u_str_nwidth(str, cmdline.cpos);
1089 skip = cw + 2 - COLS;
1090 if (skip > 0) {
1091 /* skip the ':' */
1092 skip--;
1094 /* skip rest (if any) */
1095 idx = u_skip_chars(str, &skip);
1097 width = COLS;
1098 idx = u_copy_chars(print_buffer, str + idx, &width);
1099 while (width < COLS) {
1100 /* cursor is at end of the buffer
1101 * print 1, 2 or 3 spaces
1103 * To clarify:
1105 * If the last _skipped_ character was double-width we may need
1106 * to print 2 spaces.
1108 * If the last _skipped_ character was invalid UTF-8 we may need
1109 * to print 3 spaces.
1111 print_buffer[idx++] = ' ';
1112 width++;
1114 print_buffer[idx] = 0;
1115 dump_buffer(print_buffer);
1116 } else {
1117 /* print ':' + COLS - 1 chars */
1118 addch(ch);
1119 width = COLS - 1;
1120 idx = u_copy_chars(print_buffer, str, &width);
1121 print_buffer[idx] = 0;
1122 dump_buffer(print_buffer);
1127 /* lock player_info! */
1128 static const char *get_stream_title(void)
1130 static char stream_title[255 * 16 + 1];
1131 char *ptr, *title;
1133 ptr = strstr(player_info.metadata, "StreamTitle='");
1134 if (ptr == NULL)
1135 return NULL;
1136 ptr += 13;
1137 title = ptr;
1138 while (*ptr) {
1139 if (*ptr == '\'' && *(ptr + 1) == ';') {
1140 memcpy(stream_title, title, ptr - title);
1141 stream_title[ptr - title] = 0;
1142 return stream_title;
1144 ptr++;
1146 return NULL;
1149 static void set_title(const char *title)
1151 if (!set_term_title)
1152 return;
1154 if (t_ts) {
1155 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1156 fflush(stdout);
1160 static void do_update_titleline(void)
1162 bkgdset(pairs[CURSED_TITLELINE]);
1163 player_info_lock();
1164 if (cur_track_info) {
1165 int i, use_alt_format = 0;
1166 char *wtitle;
1168 fill_track_fopts_track_info(cur_track_info);
1169 if (is_url(cur_track_info->filename)) {
1170 const char *title = get_stream_title();
1172 if (title == NULL)
1173 use_alt_format = 1;
1174 fopt_set_str(&track_fopts[TF_TITLE], title);
1175 } else {
1176 use_alt_format = !track_info_has_tag(cur_track_info);
1179 if (use_alt_format) {
1180 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1181 } else {
1182 format_print(print_buffer, COLS, current_format, track_fopts);
1184 dump_print_buffer(LINES - 3, 0);
1186 /* set window title */
1187 if (use_alt_format) {
1188 format_print(print_buffer, sizeof(print_buffer) - 1,
1189 window_title_alt_format, track_fopts);
1190 } else {
1191 format_print(print_buffer, sizeof(print_buffer) - 1,
1192 window_title_format, track_fopts);
1195 /* remove whitespace */
1196 i = strlen(print_buffer) - 1;
1197 while (i > 0 && print_buffer[i] == ' ')
1198 i--;
1199 print_buffer[i + 1] = 0;
1201 if (using_utf8) {
1202 wtitle = print_buffer;
1203 } else {
1204 utf8_decode(print_buffer);
1205 wtitle = conv_buffer;
1208 set_title(wtitle);
1209 } else {
1210 move(LINES - 3, 0);
1211 clrtoeol();
1213 set_title("cmus " VERSION);
1215 player_info_unlock();
1218 static int cmdline_cursor_column(void)
1220 char *str;
1221 int cw, skip, s;
1223 str = cmdline.line;
1224 if (!using_utf8) {
1225 /* see do_update_commandline */
1226 utf8_encode(cmdline.line);
1227 str = conv_buffer;
1230 /* width of the text in the buffer before cursor */
1231 cw = u_str_nwidth(str, cmdline.cpos);
1233 if (1 + cw < COLS) {
1234 /* whole line is visible */
1235 return 1 + cw;
1238 /* beginning of cmdline is not visible */
1240 /* check if the first visible char in cmdline would be halved
1241 * double-width character (or invalid byte <xx>) which is not possible.
1242 * we need to skip the whole character and move cursor to COLS - 2
1243 * column. */
1244 skip = cw + 2 - COLS;
1246 /* skip the ':' */
1247 skip--;
1249 /* skip rest */
1250 s = skip;
1251 u_skip_chars(str, &s);
1252 if (s > skip) {
1253 /* the last skipped char was double-width or <xx> */
1254 return COLS - 1 - (s - skip);
1256 return COLS - 1;
1259 static void post_update(void)
1261 /* refresh makes cursor visible at least for urxvt */
1262 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1263 move(LINES - 1, cmdline_cursor_column());
1264 refresh();
1265 curs_set(1);
1266 } else {
1267 if (cursor_x >= 0) {
1268 move(cursor_y, cursor_x);
1269 } else {
1270 move(LINES - 1, 0);
1272 refresh();
1273 curs_set(0);
1277 void update_titleline(void)
1279 curs_set(0);
1280 do_update_titleline();
1281 post_update();
1284 void update_full(void)
1286 if (!ui_initialized)
1287 return;
1289 curs_set(0);
1291 do_update_view(1);
1292 do_update_titleline();
1293 do_update_statusline();
1294 do_update_commandline();
1296 post_update();
1299 static void update_commandline(void)
1301 curs_set(0);
1302 do_update_commandline();
1303 post_update();
1306 void update_statusline(void)
1308 if (!ui_initialized)
1309 return;
1311 curs_set(0);
1312 do_update_statusline();
1313 post_update();
1316 void info_msg(const char *format, ...)
1318 va_list ap;
1320 va_start(ap, format);
1321 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1322 va_end(ap);
1324 msg_is_error = 0;
1326 update_commandline();
1329 void error_msg(const char *format, ...)
1331 va_list ap;
1333 if (!display_errors)
1334 return;
1336 strcpy(error_buf, "Error: ");
1337 va_start(ap, format);
1338 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1339 va_end(ap);
1341 msg_is_error = 1;
1342 error_count++;
1344 if (ui_initialized) {
1345 error_time = time(NULL);
1346 update_commandline();
1347 } else {
1348 warn("%s\n", error_buf);
1349 error_buf[0] = 0;
1353 int yes_no_query(const char *format, ...)
1355 char buffer[512];
1356 va_list ap;
1357 int ret = 0;
1359 va_start(ap, format);
1360 vsnprintf(buffer, sizeof(buffer), format, ap);
1361 va_end(ap);
1363 move(LINES - 1, 0);
1364 bkgdset(pairs[CURSED_INFO]);
1366 /* no need to convert buffer.
1367 * it is always encoded in the right charset (assuming filenames are
1368 * encoded in same charset as LC_CTYPE).
1371 addstr(buffer);
1372 clrtoeol();
1373 refresh();
1375 while (1) {
1376 int ch = getch();
1378 if (ch == ERR || ch == 0)
1379 continue;
1380 if (ch == 'y')
1381 ret = 1;
1382 break;
1384 update_commandline();
1385 return ret;
1388 void search_not_found(void)
1390 const char *what = "Track";
1392 if (search_restricted) {
1393 switch (cur_view) {
1394 case TREE_VIEW:
1395 what = "Artist/album";
1396 break;
1397 case SORTED_VIEW:
1398 case PLAYLIST_VIEW:
1399 case QUEUE_VIEW:
1400 what = "Title";
1401 break;
1402 case BROWSER_VIEW:
1403 what = "File/Directory";
1404 break;
1405 case FILTERS_VIEW:
1406 what = "Filter";
1407 break;
1408 case HELP_VIEW:
1409 what = "Binding/command/option";
1410 break;
1412 } else {
1413 switch (cur_view) {
1414 case TREE_VIEW:
1415 case SORTED_VIEW:
1416 case PLAYLIST_VIEW:
1417 case QUEUE_VIEW:
1418 what = "Track";
1419 break;
1420 case BROWSER_VIEW:
1421 what = "File/Directory";
1422 break;
1423 case FILTERS_VIEW:
1424 what = "Filter";
1425 break;
1426 case HELP_VIEW:
1427 what = "Binding/command/option";
1428 break;
1431 info_msg("%s not found: %s", what, search_str ? : "");
1434 void set_view(int view)
1436 if (view == cur_view)
1437 return;
1439 cur_view = view;
1440 switch (cur_view) {
1441 case TREE_VIEW:
1442 searchable = tree_searchable;
1443 break;
1444 case SORTED_VIEW:
1445 searchable = lib_editable.searchable;
1446 break;
1447 case PLAYLIST_VIEW:
1448 searchable = pl_editable.searchable;
1449 break;
1450 case QUEUE_VIEW:
1451 searchable = pq_editable.searchable;
1452 break;
1453 case BROWSER_VIEW:
1454 searchable = browser_searchable;
1455 break;
1456 case FILTERS_VIEW:
1457 searchable = filters_searchable;
1458 break;
1459 case HELP_VIEW:
1460 searchable = help_searchable;
1461 update_help_window();
1462 break;
1465 curs_set(0);
1466 do_update_view(1);
1467 post_update();
1470 void enter_command_mode(void)
1472 error_buf[0] = 0;
1473 error_time = 0;
1474 input_mode = COMMAND_MODE;
1475 update_commandline();
1478 void enter_search_mode(void)
1480 error_buf[0] = 0;
1481 error_time = 0;
1482 input_mode = SEARCH_MODE;
1483 search_direction = SEARCH_FORWARD;
1484 update_commandline();
1487 void enter_search_backward_mode(void)
1489 error_buf[0] = 0;
1490 error_time = 0;
1491 input_mode = SEARCH_MODE;
1492 search_direction = SEARCH_BACKWARD;
1493 update_commandline();
1496 void quit(void)
1498 running = 0;
1501 void update_colors(void)
1503 int i;
1505 if (!ui_initialized)
1506 return;
1508 for (i = 0; i < NR_CURSED; i++) {
1509 int bg = colors[cursed_to_bg_idx[i]];
1510 int fg = colors[cursed_to_fg_idx[i]];
1511 int pair = i + 1;
1513 if (fg >= 8 && fg <= 15) {
1514 /* fg colors 8..15 are special (0..7 + bold) */
1515 init_pair(pair, fg & 7, bg);
1516 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1517 } else {
1518 init_pair(pair, fg, bg);
1519 pairs[i] = COLOR_PAIR(pair);
1524 static void clear_error(void)
1526 time_t t = time(NULL);
1528 /* error msg is visible at least 3s */
1529 if (t - error_time < 3)
1530 return;
1532 if (error_buf[0]) {
1533 error_time = 0;
1534 error_buf[0] = 0;
1535 update_commandline();
1539 /* screen updates }}} */
1541 static void spawn_status_program(void)
1543 static const char *status_strs[] = { "stopped", "playing", "paused" };
1544 const char *stream_title = NULL;
1545 char *argv[32];
1546 int i, status;
1548 if (status_display_program == NULL || status_display_program[0] == 0)
1549 return;
1551 player_info_lock();
1552 status = player_info.status;
1553 if (status == 1)
1554 stream_title = get_stream_title();
1555 player_info_unlock();
1557 i = 0;
1558 argv[i++] = xstrdup(status_display_program);
1560 argv[i++] = xstrdup("status");
1561 argv[i++] = xstrdup(status_strs[status]);
1562 if (cur_track_info) {
1563 static const char *keys[] = {
1564 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1566 int j;
1568 if (is_url(cur_track_info->filename)) {
1569 argv[i++] = xstrdup("url");
1570 argv[i++] = xstrdup(cur_track_info->filename);
1571 if (stream_title) {
1572 argv[i++] = xstrdup("title");
1573 argv[i++] = xstrdup(stream_title);
1575 } else {
1576 char buf[32];
1578 argv[i++] = xstrdup("file");
1579 argv[i++] = xstrdup(cur_track_info->filename);
1580 for (j = 0; keys[j]; j++) {
1581 const char *key = keys[j];
1582 const char *val;
1584 val = comments_get_val(cur_track_info->comments, key);
1585 if (val) {
1586 argv[i++] = xstrdup(key);
1587 argv[i++] = xstrdup(val);
1590 snprintf(buf, sizeof(buf), "%d", cur_track_info->duration);
1591 argv[i++] = xstrdup("duration");
1592 argv[i++] = xstrdup(buf);
1595 argv[i++] = NULL;
1596 if (spawn(argv, &status) == -1)
1597 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1598 for (i = 0; argv[i]; i++)
1599 free(argv[i]);
1602 static int ctrl_c_pressed = 0;
1604 static void sig_int(int sig)
1606 ctrl_c_pressed = 1;
1609 static int needs_to_resize = 1;
1611 static void sig_winch(int sig)
1613 needs_to_resize = 1;
1616 static int get_window_size(int *lines, int *columns)
1618 struct winsize ws;
1620 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1621 return -1;
1622 *columns = ws.ws_col;
1623 *lines = ws.ws_row;
1624 return 0;
1627 static void resize_tree_view(int w, int h)
1629 tree_win_w = w / 3;
1630 track_win_w = w - tree_win_w - 1;
1631 if (tree_win_w < 8)
1632 tree_win_w = 8;
1633 if (track_win_w < 8)
1634 track_win_w = 8;
1635 tree_win_x = 0;
1636 tree_win_y = 0;
1637 track_win_x = tree_win_w + 1;
1638 track_win_y = 0;
1640 h--;
1641 window_set_nr_rows(lib_tree_win, h);
1642 window_set_nr_rows(lib_track_win, h);
1645 static void update(void)
1647 int needs_view_update = 0;
1648 int needs_title_update = 0;
1649 int needs_status_update = 0;
1650 int needs_command_update = 0;
1651 int needs_spawn = 0;
1653 if (needs_to_resize) {
1654 int w, h;
1655 int columns, lines;
1657 if (get_window_size(&lines, &columns) == 0) {
1658 needs_to_resize = 0;
1659 resizeterm(lines, columns);
1660 w = COLS;
1661 h = LINES - 3;
1662 if (w < 16)
1663 w = 16;
1664 if (h < 8)
1665 h = 8;
1666 editable_lock();
1667 resize_tree_view(w, h);
1668 window_set_nr_rows(lib_editable.win, h - 1);
1669 window_set_nr_rows(pl_editable.win, h - 1);
1670 window_set_nr_rows(pq_editable.win, h - 1);
1671 window_set_nr_rows(filters_win, h - 1);
1672 window_set_nr_rows(help_win, h - 1);
1673 window_set_nr_rows(browser_win, h - 1);
1674 editable_unlock();
1675 needs_title_update = 1;
1676 needs_status_update = 1;
1677 needs_command_update = 1;
1681 player_info_lock();
1682 editable_lock();
1684 needs_spawn = player_info.status_changed || player_info.file_changed ||
1685 player_info.metadata_changed;
1687 if (player_info.file_changed) {
1688 if (cur_track_info)
1689 track_info_unref(cur_track_info);
1690 if (player_info.filename[0] == 0) {
1691 cur_track_info = NULL;
1692 } else {
1693 cur_track_info = cmus_get_track_info(player_info.filename);
1695 player_info.file_changed = 0;
1696 needs_title_update = 1;
1697 needs_status_update = 1;
1699 if (player_info.metadata_changed) {
1700 player_info.metadata_changed = 0;
1701 needs_title_update = 1;
1703 if (player_info.position_changed || player_info.status_changed || player_info.vol_changed) {
1704 player_info.position_changed = 0;
1705 player_info.status_changed = 0;
1706 player_info.vol_changed = 0;
1708 needs_status_update = 1;
1710 switch (cur_view) {
1711 case TREE_VIEW:
1712 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
1713 break;
1714 case SORTED_VIEW:
1715 needs_view_update += lib_editable.win->changed;
1716 break;
1717 case PLAYLIST_VIEW:
1718 needs_view_update += pl_editable.win->changed;
1719 break;
1720 case QUEUE_VIEW:
1721 needs_view_update += pq_editable.win->changed;
1722 break;
1723 case BROWSER_VIEW:
1724 needs_view_update += browser_win->changed;
1725 break;
1726 case FILTERS_VIEW:
1727 needs_view_update += filters_win->changed;
1728 break;
1729 case HELP_VIEW:
1730 needs_view_update += help_win->changed;
1731 break;
1734 /* total time changed? */
1735 if (play_library) {
1736 needs_status_update += lib_editable.win->changed;
1737 lib_editable.win->changed = 0;
1738 } else {
1739 needs_status_update += pl_editable.win->changed;
1740 lib_editable.win->changed = 0;
1743 editable_unlock();
1744 player_info_unlock();
1746 if (needs_spawn)
1747 spawn_status_program();
1749 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1750 curs_set(0);
1752 if (needs_view_update)
1753 do_update_view(0);
1754 if (needs_title_update)
1755 do_update_titleline();
1756 if (needs_status_update)
1757 do_update_statusline();
1758 if (needs_command_update)
1759 do_update_commandline();
1760 post_update();
1764 static void handle_ch(uchar ch)
1766 clear_error();
1767 if (input_mode == NORMAL_MODE) {
1768 normal_mode_ch(ch);
1769 } else if (input_mode == COMMAND_MODE) {
1770 command_mode_ch(ch);
1771 update_commandline();
1772 } else if (input_mode == SEARCH_MODE) {
1773 search_mode_ch(ch);
1774 update_commandline();
1778 static void handle_key(int key)
1780 clear_error();
1781 if (input_mode == NORMAL_MODE) {
1782 normal_mode_key(key);
1783 } else if (input_mode == COMMAND_MODE) {
1784 command_mode_key(key);
1785 update_commandline();
1786 } else if (input_mode == SEARCH_MODE) {
1787 search_mode_key(key);
1788 update_commandline();
1792 static void u_getch(void)
1794 int key;
1795 int bit = 7;
1796 int mask = (1 << 7);
1797 uchar u, ch;
1799 key = getch();
1800 if (key == ERR || key == 0)
1801 return;
1803 if (key > 255) {
1804 handle_key(key);
1805 return;
1808 ch = (unsigned char)key;
1809 while (bit > 0 && ch & mask) {
1810 mask >>= 1;
1811 bit--;
1813 if (bit == 7) {
1814 /* ascii */
1815 u = ch;
1816 } else {
1817 int count;
1819 u = ch & ((1 << bit) - 1);
1820 count = 6 - bit;
1821 while (count) {
1822 key = getch();
1823 if (key == ERR || key == 0)
1824 return;
1826 ch = (unsigned char)key;
1827 u = (u << 6) | (ch & 63);
1828 count--;
1831 handle_ch(u);
1834 static void main_loop(void)
1836 int rc, fd_high;
1838 fd_high = server_socket;
1839 while (running) {
1840 fd_set set;
1841 struct timeval tv;
1843 update();
1845 FD_ZERO(&set);
1846 FD_SET(0, &set);
1847 FD_SET(server_socket, &set);
1849 /* Timeout must be so small that screen updates seem instant.
1850 * Only affects changes done in other threads (worker, player).
1852 * Too small timeout makes window updates too fast (wastes CPU).
1854 * Too large timeout makes status line (position) updates too slow.
1855 * The timeout is accuracy of player position.
1857 tv.tv_sec = 0;
1858 tv.tv_usec = 100e3;
1859 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1860 if (rc <= 0) {
1861 if (ctrl_c_pressed) {
1862 handle_ch(0x03);
1863 ctrl_c_pressed = 0;
1865 continue;
1868 if (FD_ISSET(server_socket, &set)) {
1869 /* no error msgs for cmus-remote */
1870 display_errors = 0;
1872 server_serve();
1874 if (FD_ISSET(0, &set)) {
1875 /* diplay errors for interactive commands */
1876 display_errors = 1;
1878 if (using_utf8) {
1879 u_getch();
1880 } else {
1881 int key = getch();
1883 if (key != ERR && key != 0) {
1884 if (key > 255) {
1885 handle_key(key);
1886 } else {
1887 uchar ch = key;
1889 if (ch > 0x7f)
1890 ch |= U_INVALID_MASK;
1891 handle_ch(ch);
1899 static int get_next(char **filename)
1901 struct track_info *info;
1903 editable_lock();
1904 info = play_queue_remove();
1905 if (info == NULL) {
1906 if (play_library) {
1907 info = lib_set_next();
1908 } else {
1909 info = pl_set_next();
1912 editable_unlock();
1914 if (info == NULL)
1915 return -1;
1917 *filename = xstrdup(info->filename);
1918 track_info_unref(info);
1919 return 0;
1922 static const struct player_callbacks player_callbacks = {
1923 .get_next = get_next
1926 static void init_curses(void)
1928 struct sigaction act;
1929 char *ptr, *term;
1931 sigemptyset(&act.sa_mask);
1932 act.sa_flags = 0;
1933 act.sa_handler = sig_int;
1934 sigaction(SIGINT, &act, NULL);
1936 sigemptyset(&act.sa_mask);
1937 act.sa_flags = 0;
1938 act.sa_handler = sig_winch;
1939 sigaction(SIGWINCH, &act, NULL);
1941 initscr();
1943 /* turn off kb buffering */
1944 cbreak();
1946 keypad(stdscr, TRUE);
1948 /* wait max 5 * 0.1 s if there are no keys available
1949 * doesn't really matter because we use select()
1951 halfdelay(5);
1953 noecho();
1954 if (has_colors()) {
1955 start_color();
1956 use_default_colors();
1958 d_print("Number of supported colors: %d\n", COLORS);
1959 ui_initialized = 1;
1961 /* this was disabled while initializing because it needs to be
1962 * called only once after all colors have been set
1964 update_colors();
1966 ptr = print_buffer;
1967 t_ts = tgetstr("ts", &ptr);
1968 t_fs = tgetstr("fs", &ptr);
1969 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
1971 if (!t_fs)
1972 t_ts = NULL;
1974 if (!t_ts && (term = getenv("TERM"))) {
1976 * Eterm: Eterm
1977 * aterm: rxvt
1978 * mlterm: xterm
1979 * terminal (xfce): xterm
1980 * urxvt: rxvt-unicode
1981 * xterm: xterm, xterm-{,16,88,256}color
1983 if (!strcmp(term, "screen")) {
1984 t_ts = "\033k";
1985 t_fs = "\033\\";
1986 } else if (!strncmp(term, "xterm", 5) ||
1987 !strncmp(term, "rxvt", 4) ||
1988 !strcmp(term, "Eterm")) {
1989 /* \033]1; change icon
1990 * \033]2; change title
1991 * \033]0; change both
1993 t_ts = "\033]0;";
1994 t_fs = "\007";
1999 static void init_all(void)
2001 server_init(server_address);
2003 /* does not select output plugin */
2004 player_init(&player_callbacks);
2006 /* plugins have been loaded so we know what plugin options are available */
2007 options_add();
2009 lib_init();
2010 searchable = tree_searchable;
2011 pl_init();
2012 cmus_init();
2013 browser_init();
2014 filters_init();
2015 help_init();
2016 cmdline_init();
2017 commands_init();
2018 search_mode_init();
2020 /* almost everything must be initialized now */
2021 options_load();
2023 /* finally we can set the output plugin */
2024 player_set_op(output_plugin);
2026 player_get_volume(&player_info.vol_left, &player_info.vol_right);
2028 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
2029 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
2030 pl_filename = xstrdup(pl_autosave_filename);
2031 lib_filename = xstrdup(lib_autosave_filename);
2033 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
2034 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
2036 if (error_count) {
2037 char buf[16];
2039 warn("Press <enter> to continue.");
2040 fgets(buf, sizeof(buf), stdin);
2042 help_add_all_unbound();
2044 init_curses();
2047 static void exit_all(void)
2049 endwin();
2051 options_exit();
2053 server_exit();
2054 cmus_exit();
2055 cmus_save(lib_for_each, lib_autosave_filename);
2056 cmus_save(pl_for_each, pl_autosave_filename);
2058 player_exit();
2059 commands_exit();
2060 search_mode_exit();
2061 filters_exit();
2062 help_exit();
2063 browser_exit();
2066 enum {
2067 FLAG_LISTEN,
2068 FLAG_PLUGINS,
2069 FLAG_HELP,
2070 FLAG_VERSION,
2071 NR_FLAGS
2074 static struct option options[NR_FLAGS + 1] = {
2075 { 0, "listen", 1 },
2076 { 0, "plugins", 0 },
2077 { 0, "help", 0 },
2078 { 0, "version", 0 },
2079 { 0, NULL, 0 }
2082 static const char *usage =
2083 "Usage: %s [OPTION]...\n"
2084 "Curses based music player.\n"
2085 "\n"
2086 " --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
2087 " ADDR is either a UNIX socket or host[:port]\n"
2088 " --plugins list available plugins and exit\n"
2089 " --help display this help and exit\n"
2090 " --version " VERSION "\n"
2091 "\n"
2092 "Use cmus-remote to control cmus from command line.\n"
2093 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2095 int main(int argc, char *argv[])
2097 int list_plugins = 0;
2099 program_name = argv[0];
2100 argv++;
2101 while (1) {
2102 int idx;
2103 char *arg;
2105 idx = get_option(&argv, options, &arg);
2106 if (idx < 0)
2107 break;
2109 switch (idx) {
2110 case FLAG_HELP:
2111 printf(usage, program_name);
2112 return 0;
2113 case FLAG_VERSION:
2114 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
2115 return 0;
2116 case FLAG_PLUGINS:
2117 list_plugins = 1;
2118 break;
2119 case FLAG_LISTEN:
2120 server_address = xstrdup(arg);
2121 break;
2125 setlocale(LC_CTYPE, "");
2126 #ifdef CODESET
2127 charset = nl_langinfo(CODESET);
2128 #else
2129 charset = "ISO-8859-1";
2130 #endif
2131 if (strcmp(charset, "UTF-8") == 0) {
2132 using_utf8 = 1;
2133 } else {
2134 using_utf8 = 0;
2136 misc_init();
2137 if (server_address == NULL)
2138 server_address = xstrjoin(cmus_config_dir, "/socket");
2139 debug_init();
2140 d_print("charset = '%s'\n", charset);
2142 player_load_plugins();
2143 if (list_plugins) {
2144 player_dump_plugins();
2145 return 0;
2147 init_all();
2148 main_loop();
2149 exit_all();
2150 return 0;