flac: Saner EOF handling
[cmus.git] / ui_curses.c
blob613a84ccb821a37fe973ffcbf62fcc62da9c601e
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 show_cursor;
121 static int cursor_x;
122 static int cursor_y;
124 enum {
125 CURSED_WIN,
126 CURSED_WIN_CUR,
127 CURSED_WIN_SEL,
128 CURSED_WIN_SEL_CUR,
130 CURSED_WIN_ACTIVE,
131 CURSED_WIN_ACTIVE_CUR,
132 CURSED_WIN_ACTIVE_SEL,
133 CURSED_WIN_ACTIVE_SEL_CUR,
135 CURSED_SEPARATOR,
136 CURSED_WIN_TITLE,
137 CURSED_COMMANDLINE,
138 CURSED_STATUSLINE,
140 CURSED_TITLELINE,
141 CURSED_DIR,
142 CURSED_ERROR,
143 CURSED_INFO,
145 NR_CURSED
148 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
149 COLOR_WIN_BG,
150 COLOR_WIN_BG,
151 COLOR_WIN_INACTIVE_SEL_BG,
152 COLOR_WIN_INACTIVE_CUR_SEL_BG,
154 COLOR_WIN_BG,
155 COLOR_WIN_BG,
156 COLOR_WIN_SEL_BG,
157 COLOR_WIN_CUR_SEL_BG,
159 COLOR_WIN_BG,
160 COLOR_WIN_TITLE_BG,
161 COLOR_CMDLINE_BG,
162 COLOR_STATUSLINE_BG,
164 COLOR_TITLELINE_BG,
165 COLOR_WIN_BG,
166 COLOR_CMDLINE_BG,
167 COLOR_CMDLINE_BG
170 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
171 COLOR_WIN_FG,
172 COLOR_WIN_CUR,
173 COLOR_WIN_INACTIVE_SEL_FG,
174 COLOR_WIN_INACTIVE_CUR_SEL_FG,
176 COLOR_WIN_FG,
177 COLOR_WIN_CUR,
178 COLOR_WIN_SEL_FG,
179 COLOR_WIN_CUR_SEL_FG,
181 COLOR_SEPARATOR,
182 COLOR_WIN_TITLE_FG,
183 COLOR_CMDLINE_FG,
184 COLOR_STATUSLINE_FG,
186 COLOR_TITLELINE_FG,
187 COLOR_WIN_DIR,
188 COLOR_ERROR,
189 COLOR_INFO
192 /* index is CURSED_*, value is fucking color pair */
193 static int pairs[NR_CURSED];
195 enum {
196 TF_ARTIST,
197 TF_ALBUM,
198 TF_DISC,
199 TF_TRACK,
200 TF_TITLE,
201 TF_YEAR,
202 TF_GENRE,
203 TF_COMMENT,
204 TF_DURATION,
205 TF_PATHFILE,
206 TF_FILE,
207 NR_TFS
210 static struct format_option track_fopts[NR_TFS + 1] = {
211 DEF_FO_STR('a'),
212 DEF_FO_STR('l'),
213 DEF_FO_INT('D'),
214 DEF_FO_INT('n'),
215 DEF_FO_STR('t'),
216 DEF_FO_STR('y'),
217 DEF_FO_STR('g'),
218 DEF_FO_STR('c'),
219 DEF_FO_TIME('d'),
220 DEF_FO_STR('f'),
221 DEF_FO_STR('F'),
222 DEF_FO_END
225 enum {
226 SF_STATUS,
227 SF_POSITION,
228 SF_DURATION,
229 SF_TOTAL,
230 SF_VOLUME,
231 SF_LVOLUME,
232 SF_RVOLUME,
233 SF_BUFFER,
234 SF_REPEAT,
235 SF_CONTINUE,
236 SF_SHUFFLE,
237 SF_PLAYLISTMODE,
238 NR_SFS
241 static struct format_option status_fopts[NR_SFS + 1] = {
242 DEF_FO_STR('s'),
243 DEF_FO_TIME('p'),
244 DEF_FO_TIME('d'),
245 DEF_FO_TIME('t'),
246 DEF_FO_INT('v'),
247 DEF_FO_INT('l'),
248 DEF_FO_INT('r'),
249 DEF_FO_INT('b'),
250 DEF_FO_STR('R'),
251 DEF_FO_STR('C'),
252 DEF_FO_STR('S'),
253 DEF_FO_STR('L'),
254 DEF_FO_END
257 static void utf8_encode(const char *buffer)
259 static iconv_t cd = (iconv_t)-1;
260 size_t is, os;
261 const char *i;
262 char *o;
263 int rc;
265 if (cd == (iconv_t)-1) {
266 d_print("iconv_open(UTF-8, %s)\n", charset);
267 cd = iconv_open("UTF-8", charset);
268 if (cd == (iconv_t)-1) {
269 d_print("iconv_open failed: %s\n", strerror(errno));
270 return;
273 i = buffer;
274 o = conv_buffer;
275 is = strlen(i);
276 os = sizeof(conv_buffer) - 1;
277 rc = iconv(cd, (void *)&i, &is, &o, &os);
278 *o = 0;
279 if (rc == -1) {
280 d_print("iconv failed: %s\n", strerror(errno));
281 return;
285 static void utf8_decode(const char *buffer)
287 static iconv_t cd = (iconv_t)-1;
288 size_t is, os;
289 const char *i;
290 char *o;
291 int rc;
293 if (cd == (iconv_t)-1) {
294 d_print("iconv_open(%s, UTF-8)\n", charset);
295 cd = iconv_open(charset, "UTF-8");
296 if (cd == (iconv_t)-1) {
297 d_print("iconv_open failed: %s\n", strerror(errno));
298 return;
301 i = buffer;
302 o = conv_buffer;
303 is = strlen(i);
304 os = sizeof(conv_buffer) - 1;
305 rc = iconv(cd, (void *)&i, &is, &o, &os);
306 *o = 0;
307 if (rc == -1) {
308 d_print("iconv failed: %s\n", strerror(errno));
309 return;
313 /* screen updates {{{ */
315 static void dump_print_buffer(int row, int col)
317 if (using_utf8) {
318 mvaddstr(row, col, print_buffer);
319 } else {
320 utf8_decode(print_buffer);
321 mvaddstr(row, col, conv_buffer);
325 /* print @str into @buf
327 * if @str is shorter than @width pad with spaces
328 * if @str is wider than @width truncate and add "..."
330 static int format_str(char *buf, const char *str, int width)
332 int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;
334 while (1) {
335 uchar u;
336 int w;
338 u_get_char(str, &s, &u);
339 if (u == 0) {
340 memset(buf + d, ' ', width);
341 d += width;
342 break;
345 w = u_char_width(u);
346 if (width == 3)
347 ellipsis_pos = d;
348 if (width == 4 && w == 2) {
349 /* can't cut double-width char */
350 ellipsis_pos = d + 1;
351 cut_double_width = 1;
354 width -= w;
355 if (width < 0) {
356 /* does not fit */
357 d = ellipsis_pos;
358 if (cut_double_width) {
359 /* first half of the double-width char */
360 buf[d - 1] = ' ';
362 buf[d++] = '.';
363 buf[d++] = '.';
364 buf[d++] = '.';
365 break;
367 u_set_char(buf, &d, u);
369 return d;
372 static void sprint(int row, int col, const char *str, int width)
374 int pos = 0;
376 print_buffer[pos++] = ' ';
377 pos += format_str(print_buffer + pos, str, width - 2);
378 print_buffer[pos++] = ' ';
379 print_buffer[pos] = 0;
380 dump_print_buffer(row, col);
383 static void sprint_ascii(int row, int col, const char *str, int len)
385 int l;
387 l = strlen(str);
388 len -= 2;
390 print_buffer[0] = ' ';
391 if (l > len) {
392 memcpy(print_buffer + 1, str, len - 3);
393 print_buffer[len - 2] = '.';
394 print_buffer[len - 1] = '.';
395 print_buffer[len - 0] = '.';
396 } else {
397 memcpy(print_buffer + 1, str, l);
398 memset(print_buffer + 1 + l, ' ', len - l);
400 print_buffer[len + 1] = ' ';
401 print_buffer[len + 2] = 0;
402 mvaddstr(row, col, print_buffer);
405 static void print_tree(struct window *win, int row, struct iter *iter)
407 const char *str;
408 struct artist *artist;
409 struct album *album;
410 struct iter sel;
411 char buf[256];
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 if (album) {
437 print_buffer[pos++] = ' ';
438 print_buffer[pos++] = ' ';
439 str = album->name;
440 } else {
441 str = artist->name;
442 if (pretty_artist_name && !strncasecmp(str, "the ", 4)) {
443 snprintf(buf, sizeof(buf), "%s, The", str + 4);
444 str = buf;
447 pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
448 print_buffer[pos++] = ' ';
449 print_buffer[pos++] = 0;
450 dump_print_buffer(tree_win_y + row + 1, tree_win_x);
453 static inline void fopt_set_str(struct format_option *fopt, const char *str)
455 BUG_ON(fopt->type != FO_STR);
456 if (str) {
457 fopt->fo_str = str;
458 fopt->empty = 0;
459 } else {
460 fopt->empty = 1;
464 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
466 BUG_ON(fopt->type != FO_INT);
467 fopt->fo_int = value;
468 fopt->empty = empty;
471 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
473 BUG_ON(fopt->type != FO_TIME);
474 fopt->fo_time = value;
475 fopt->empty = empty;
478 static void fill_track_fopts_track_info(struct track_info *info)
480 char *filename;
481 int num, disc;
483 if (using_utf8) {
484 filename = info->filename;
485 } else {
486 utf8_encode(info->filename);
487 filename = conv_buffer;
489 disc = comments_get_int(info->comments, "discnumber");
490 num = comments_get_int(info->comments, "tracknumber");
492 fopt_set_str(&track_fopts[TF_ARTIST], keyvals_get_val(info->comments, "artist"));
493 fopt_set_str(&track_fopts[TF_ALBUM], keyvals_get_val(info->comments, "album"));
494 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
495 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
496 fopt_set_str(&track_fopts[TF_TITLE], keyvals_get_val(info->comments, "title"));
497 fopt_set_str(&track_fopts[TF_YEAR], keyvals_get_val(info->comments, "date"));
498 fopt_set_str(&track_fopts[TF_GENRE], keyvals_get_val(info->comments, "genre"));
499 fopt_set_str(&track_fopts[TF_COMMENT], keyvals_get_val(info->comments, "comment"));
500 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
501 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
502 if (is_url(info->filename)) {
503 fopt_set_str(&track_fopts[TF_FILE], filename);
504 } else {
505 const char *f;
507 f = strrchr(filename, '/');
508 if (f) {
509 fopt_set_str(&track_fopts[TF_FILE], f + 1);
510 } else {
511 fopt_set_str(&track_fopts[TF_FILE], filename);
516 static void print_track(struct window *win, int row, struct iter *iter)
518 struct tree_track *track;
519 struct iter sel;
520 int current, selected, active;
522 track = iter_to_tree_track(iter);
523 current = lib_cur_track == track;
524 window_get_sel(win, &sel);
525 selected = iters_equal(iter, &sel);
526 active = lib_cur_win == lib_track_win;
527 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
529 if (active && selected) {
530 cursor_x = track_win_x;
531 cursor_y = 1 + row;
534 fill_track_fopts_track_info(tree_track_info(track));
536 if (track_info_has_tag(tree_track_info(track))) {
537 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
538 } else {
539 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
541 dump_print_buffer(track_win_y + row + 1, track_win_x);
544 /* used by print_editable only */
545 static struct simple_track *current_track;
547 static void print_editable(struct window *win, int row, struct iter *iter)
549 struct simple_track *track;
550 struct iter sel;
551 int current, selected, active;
553 track = iter_to_simple_track(iter);
554 current = current_track == track;
555 window_get_sel(win, &sel);
556 selected = iters_equal(iter, &sel);
558 if (selected) {
559 cursor_x = 0;
560 cursor_y = 1 + row;
563 active = 1;
564 if (!selected && track->marked) {
565 selected = 1;
566 active = 0;
569 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
571 fill_track_fopts_track_info(track->info);
573 if (track_info_has_tag(track->info)) {
574 format_print(print_buffer, COLS, list_win_format, track_fopts);
575 } else {
576 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
578 dump_print_buffer(row + 1, 0);
581 static void print_browser(struct window *win, int row, struct iter *iter)
583 struct browser_entry *e;
584 struct iter sel;
585 int selected;
587 e = iter_to_browser_entry(iter);
588 window_get_sel(win, &sel);
589 selected = iters_equal(iter, &sel);
590 if (selected) {
591 int active = 1;
592 int current = 0;
594 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
595 } else {
596 if (e->type == BROWSER_ENTRY_DIR) {
597 bkgdset(pairs[CURSED_DIR]);
598 } else {
599 bkgdset(pairs[CURSED_WIN]);
603 if (selected) {
604 cursor_x = 0;
605 cursor_y = 1 + row;
608 /* file name encoding == terminal encoding. no need to convert */
609 if (using_utf8) {
610 sprint(row + 1, 0, e->name, COLS);
611 } else {
612 sprint_ascii(row + 1, 0, e->name, COLS);
616 static void print_filter(struct window *win, int row, struct iter *iter)
618 char buf[256];
619 struct filter_entry *e = iter_to_filter_entry(iter);
620 struct iter sel;
621 /* window active? */
622 int active = 1;
623 /* row selected? */
624 int selected;
625 /* is the filter currently active? */
626 int current = !!e->act_stat;
627 const char stat_chars[3] = " *!";
628 int ch1, ch2, ch3, pos;
630 window_get_sel(win, &sel);
631 selected = iters_equal(iter, &sel);
632 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
634 if (selected) {
635 cursor_x = 0;
636 cursor_y = 1 + row;
639 ch1 = ' ';
640 ch3 = ' ';
641 if (e->sel_stat != e->act_stat) {
642 ch1 = '[';
643 ch3 = ']';
645 ch2 = stat_chars[e->sel_stat];
646 snprintf(buf, sizeof(buf), "%c%c%c%-15s %s", ch1, ch2, ch3, e->name, e->filter);
647 pos = format_str(print_buffer, buf, COLS - 1);
648 print_buffer[pos++] = ' ';
649 print_buffer[pos] = 0;
650 dump_print_buffer(row + 1, 0);
653 static void print_help(struct window *win, int row, struct iter *iter)
655 struct iter sel;
656 int selected;
657 int pos;
658 int active = 1;
659 char buf[512];
660 const struct help_entry *e = iter_to_help_entry(iter);
661 const struct cmus_opt *opt;
663 window_get_sel(win, &sel);
664 selected = iters_equal(iter, &sel);
665 bkgdset(pairs[(active << 2) | (selected << 1)]);
667 if (selected) {
668 cursor_x = 0;
669 cursor_y = 1 + row;
672 switch (e->type) {
673 case HE_TEXT:
674 snprintf(buf, sizeof(buf), " %s", e->text);
675 break;
676 case HE_BOUND:
677 snprintf(buf, sizeof(buf), " %-8s %-14s %s",
678 key_context_names[e->binding->ctx],
679 e->binding->key->name,
680 e->binding->cmd);
681 break;
682 case HE_UNBOUND:
683 snprintf(buf, sizeof(buf), " %s", e->command->name);
684 break;
685 case HE_OPTION:
686 opt = e->option;
687 snprintf(buf, sizeof(buf), " %-29s ", opt->name);
688 opt->get(opt->id, buf + strlen(buf));
689 break;
691 pos = format_str(print_buffer, buf, COLS - 1);
692 print_buffer[pos++] = ' ';
693 print_buffer[pos] = 0;
694 dump_print_buffer(row + 1, 0);
697 static void update_window(struct window *win, int x, int y, int w, const char *title,
698 void (*print)(struct window *, int, struct iter *))
700 struct iter iter;
701 int nr_rows;
702 int c, i;
704 win->changed = 0;
706 bkgdset(pairs[CURSED_WIN_TITLE]);
707 c = snprintf(print_buffer, w + 1, " %s", title);
708 if (c > w)
709 c = w;
710 memset(print_buffer + c, ' ', w - c + 1);
711 print_buffer[w] = 0;
712 dump_print_buffer(y, x);
713 nr_rows = window_get_nr_rows(win);
714 i = 0;
715 if (window_get_top(win, &iter)) {
716 while (i < nr_rows) {
717 print(win, i, &iter);
718 i++;
719 if (!window_get_next(win, &iter))
720 break;
724 bkgdset(pairs[0]);
725 memset(print_buffer, ' ', w);
726 print_buffer[w] = 0;
727 while (i < nr_rows) {
728 dump_print_buffer(y + i + 1, x);
729 i++;
733 static void update_tree_window(void)
735 update_window(lib_tree_win, tree_win_x, tree_win_y,
736 tree_win_w, "Artist / Album", print_tree);
739 static void update_track_window(void)
741 char title[512];
743 /* it doesn't matter what format options we use because the format
744 * string does not contain any format charaters */
745 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
746 update_window(lib_track_win, track_win_x, track_win_y,
747 track_win_w, title, print_track);
750 static const char *pretty(const char *path)
752 static int home_len = -1;
753 static char buf[256];
755 if (home_len == -1)
756 home_len = strlen(home_dir);
758 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
759 return path;
761 buf[0] = '~';
762 strcpy(buf + 1, path + home_len);
763 return buf;
766 static const char * const sorted_names[2] = { "", "sorted by " };
768 static void update_editable_window(struct editable *e, const char *title, const char *filename)
770 char buf[512];
771 int pos;
773 if (filename) {
774 if (using_utf8) {
775 /* already UTF-8 */
776 } else {
777 utf8_encode(filename);
778 filename = conv_buffer;
780 snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
781 pretty(filename), e->nr_tracks);
782 } else {
783 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
786 if (e->nr_marked) {
787 pos = strlen(buf);
788 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
790 pos = strlen(buf);
791 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
792 sorted_names[e->sort_str[0] != 0], e->sort_str);
794 update_window(e->win, 0, 0, COLS, buf, &print_editable);
797 static void update_sorted_window(void)
799 current_track = (struct simple_track *)lib_cur_track;
800 update_editable_window(&lib_editable, "Library", lib_filename);
803 static void update_pl_window(void)
805 current_track = pl_cur_track;
806 update_editable_window(&pl_editable, "Playlist", pl_filename);
809 static void update_play_queue_window(void)
811 current_track = NULL;
812 update_editable_window(&pq_editable, "Play Queue", NULL);
815 static void update_browser_window(void)
817 char title[512];
818 char *dirname;
820 if (using_utf8) {
821 /* already UTF-8 */
822 dirname = browser_dir;
823 } else {
824 utf8_encode(browser_dir);
825 dirname = conv_buffer;
827 snprintf(title, sizeof(title), "Browser - %s", dirname);
828 update_window(browser_win, 0, 0, COLS, title, print_browser);
831 static void update_filters_window(void)
833 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
836 static void update_help_window(void)
838 update_window(help_win, 0, 0, COLS, "Settings", print_help);
841 static void draw_separator(void)
843 int row;
845 bkgdset(pairs[CURSED_WIN_TITLE]);
846 mvaddch(0, tree_win_w, ' ');
847 bkgdset(pairs[CURSED_SEPARATOR]);
848 for (row = 1; row < LINES - 3; row++)
849 mvaddch(row, tree_win_w, ACS_VLINE);
852 static void do_update_view(int full)
854 cursor_x = -1;
855 cursor_y = -1;
857 switch (cur_view) {
858 case TREE_VIEW:
859 editable_lock();
860 if (full || lib_tree_win->changed)
861 update_tree_window();
862 if (full || lib_track_win->changed)
863 update_track_window();
864 editable_unlock();
865 draw_separator();
866 break;
867 case SORTED_VIEW:
868 editable_lock();
869 update_sorted_window();
870 editable_unlock();
871 break;
872 case PLAYLIST_VIEW:
873 editable_lock();
874 update_pl_window();
875 editable_unlock();
876 break;
877 case QUEUE_VIEW:
878 editable_lock();
879 update_play_queue_window();
880 editable_unlock();
881 break;
882 case BROWSER_VIEW:
883 update_browser_window();
884 break;
885 case FILTERS_VIEW:
886 update_filters_window();
887 break;
888 case HELP_VIEW:
889 update_help_window();
890 break;
894 static void do_update_statusline(void)
896 static const char *status_strs[] = { ".", ">", "|" };
897 static const char *cont_strs[] = { " ", "C" };
898 static const char *repeat_strs[] = { " ", "R" };
899 static const char *shuffle_strs[] = { " ", "S" };
900 int buffer_fill, vol, vol_left, vol_right;
901 int duration = -1;
902 char *msg;
903 char format[80];
905 editable_lock();
906 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
907 pl_editable.total_time, 0);
908 editable_unlock();
910 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
911 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
912 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
914 player_info_lock();
916 if (player_info.ti)
917 duration = player_info.ti->duration;
919 if (soft_vol) {
920 vol_left = soft_vol_l;
921 vol_right = soft_vol_r;
922 vol = (vol_left + vol_right + 1) / 2;
923 } else if (!volume_max) {
924 vol_left = vol_right = vol = -1;
925 } else {
926 vol_left = scale_to_percentage(volume_l, volume_max);
927 vol_right = scale_to_percentage(volume_r, volume_max);
928 vol = (vol_left + vol_right + 1) / 2;
930 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
932 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
934 if (show_remaining_time && duration != -1) {
935 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
936 } else {
937 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
940 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
941 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
942 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
943 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
944 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
945 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
947 strcpy(format, " %s %p ");
948 if (duration != -1)
949 strcat(format, "/ %d ");
950 strcat(format, "- %t ");
951 if (vol >= 0) {
952 if (vol_left != vol_right) {
953 strcat(format, "vol: %l,%r ");
954 } else {
955 strcat(format, "vol: %v ");
958 if (player_info.ti && is_url(player_info.ti->filename))
959 strcat(format, "buf: %b ");
960 strcat(format, "%=");
961 if (player_repeat_current) {
962 strcat(format, "repeat current");
963 } else if (play_library) {
964 /* artist/album modes work only in lib */
965 if (shuffle) {
966 /* shuffle overrides sorted mode */
967 strcat(format, "%L from library");
968 } else if (play_sorted) {
969 strcat(format, "%L from sorted library");
970 } else {
971 strcat(format, "%L from library");
973 } else {
974 strcat(format, "playlist");
976 strcat(format, " | %1C%1R%1S ");
977 format_print(print_buffer, COLS, format, status_fopts);
979 msg = player_info.error_msg;
980 player_info.error_msg = NULL;
982 player_info_unlock();
984 bkgdset(pairs[CURSED_STATUSLINE]);
985 dump_print_buffer(LINES - 2, 0);
987 if (msg) {
988 error_msg("%s", msg);
989 free(msg);
993 static void dump_buffer(const char *buffer)
995 if (using_utf8) {
996 addstr(buffer);
997 } else {
998 utf8_decode(buffer);
999 addstr(conv_buffer);
1003 static void do_update_commandline(void)
1005 char *str;
1006 int w, idx;
1007 char ch;
1009 move(LINES - 1, 0);
1010 if (error_buf[0]) {
1011 if (msg_is_error) {
1012 bkgdset(pairs[CURSED_ERROR]);
1013 } else {
1014 bkgdset(pairs[CURSED_INFO]);
1016 addstr(error_buf);
1017 clrtoeol();
1018 return;
1020 bkgdset(pairs[CURSED_COMMANDLINE]);
1021 if (input_mode == NORMAL_MODE) {
1022 clrtoeol();
1023 return;
1026 str = cmdline.line;
1027 if (!using_utf8) {
1028 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1029 * characters are invalid UTF-8 so it really is in locale's
1030 * encoding.
1032 * This code should be safe because cmdline.bpos ==
1033 * cmdline.cpos as every non-ASCII character is counted as one
1034 * invalid UTF-8 byte.
1036 * NOTE: This has nothing to do with widths of printed
1037 * characters. I.e. even if there were control characters
1038 * (displayed as <xx>) there would be no problem because bpos
1039 * still equals to cpos, I think.
1041 utf8_encode(cmdline.line);
1042 str = conv_buffer;
1045 /* COMMAND_MODE or SEARCH_MODE */
1046 w = u_str_width(str);
1047 ch = ':';
1048 if (input_mode == SEARCH_MODE)
1049 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
1051 if (w <= COLS - 2) {
1052 addch(ch);
1053 idx = u_copy_chars(print_buffer, str, &w);
1054 print_buffer[idx] = 0;
1055 dump_buffer(print_buffer);
1056 clrtoeol();
1057 } else {
1058 /* keep cursor as far right as possible */
1059 int skip, width, cw;
1061 /* cursor pos (width, not chars. doesn't count the ':') */
1062 cw = u_str_nwidth(str, cmdline.cpos);
1064 skip = cw + 2 - COLS;
1065 if (skip > 0) {
1066 /* skip the ':' */
1067 skip--;
1069 /* skip rest (if any) */
1070 idx = u_skip_chars(str, &skip);
1072 width = COLS;
1073 idx = u_copy_chars(print_buffer, str + idx, &width);
1074 while (width < COLS) {
1075 /* cursor is at end of the buffer
1076 * print 1, 2 or 3 spaces
1078 * To clarify:
1080 * If the last _skipped_ character was double-width we may need
1081 * to print 2 spaces.
1083 * If the last _skipped_ character was invalid UTF-8 we may need
1084 * to print 3 spaces.
1086 print_buffer[idx++] = ' ';
1087 width++;
1089 print_buffer[idx] = 0;
1090 dump_buffer(print_buffer);
1091 } else {
1092 /* print ':' + COLS - 1 chars */
1093 addch(ch);
1094 width = COLS - 1;
1095 idx = u_copy_chars(print_buffer, str, &width);
1096 print_buffer[idx] = 0;
1097 dump_buffer(print_buffer);
1102 /* lock player_info! */
1103 static const char *get_stream_title(void)
1105 static char stream_title[255 * 16 + 1];
1106 char *ptr, *title;
1108 ptr = strstr(player_info.metadata, "StreamTitle='");
1109 if (ptr == NULL)
1110 return NULL;
1111 ptr += 13;
1112 title = ptr;
1113 while (*ptr) {
1114 if (*ptr == '\'' && *(ptr + 1) == ';') {
1115 memcpy(stream_title, title, ptr - title);
1116 stream_title[ptr - title] = 0;
1117 return stream_title;
1119 ptr++;
1121 return NULL;
1124 static void set_title(const char *title)
1126 if (!set_term_title)
1127 return;
1129 if (t_ts) {
1130 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1131 fflush(stdout);
1135 static void do_update_titleline(void)
1137 bkgdset(pairs[CURSED_TITLELINE]);
1138 player_info_lock();
1139 if (player_info.ti) {
1140 int i, use_alt_format = 0;
1141 char *wtitle;
1143 fill_track_fopts_track_info(player_info.ti);
1144 if (is_url(player_info.ti->filename)) {
1145 const char *title = get_stream_title();
1147 if (title == NULL)
1148 use_alt_format = 1;
1149 fopt_set_str(&track_fopts[TF_TITLE], title);
1150 } else {
1151 use_alt_format = !track_info_has_tag(player_info.ti);
1154 if (use_alt_format) {
1155 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1156 } else {
1157 format_print(print_buffer, COLS, current_format, track_fopts);
1159 dump_print_buffer(LINES - 3, 0);
1161 /* set window title */
1162 if (use_alt_format) {
1163 format_print(print_buffer, sizeof(print_buffer) - 1,
1164 window_title_alt_format, track_fopts);
1165 } else {
1166 format_print(print_buffer, sizeof(print_buffer) - 1,
1167 window_title_format, track_fopts);
1170 /* remove whitespace */
1171 i = strlen(print_buffer) - 1;
1172 while (i > 0 && print_buffer[i] == ' ')
1173 i--;
1174 print_buffer[i + 1] = 0;
1176 if (using_utf8) {
1177 wtitle = print_buffer;
1178 } else {
1179 utf8_decode(print_buffer);
1180 wtitle = conv_buffer;
1183 set_title(wtitle);
1184 } else {
1185 move(LINES - 3, 0);
1186 clrtoeol();
1188 set_title("cmus " VERSION);
1190 player_info_unlock();
1193 static int cmdline_cursor_column(void)
1195 char *str;
1196 int cw, skip, s;
1198 str = cmdline.line;
1199 if (!using_utf8) {
1200 /* see do_update_commandline */
1201 utf8_encode(cmdline.line);
1202 str = conv_buffer;
1205 /* width of the text in the buffer before cursor */
1206 cw = u_str_nwidth(str, cmdline.cpos);
1208 if (1 + cw < COLS) {
1209 /* whole line is visible */
1210 return 1 + cw;
1213 /* beginning of cmdline is not visible */
1215 /* check if the first visible char in cmdline would be halved
1216 * double-width character (or invalid byte <xx>) which is not possible.
1217 * we need to skip the whole character and move cursor to COLS - 2
1218 * column. */
1219 skip = cw + 2 - COLS;
1221 /* skip the ':' */
1222 skip--;
1224 /* skip rest */
1225 s = skip;
1226 u_skip_chars(str, &s);
1227 if (s > skip) {
1228 /* the last skipped char was double-width or <xx> */
1229 return COLS - 1 - (s - skip);
1231 return COLS - 1;
1234 static void post_update(void)
1236 /* refresh makes cursor visible at least for urxvt */
1237 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1238 move(LINES - 1, cmdline_cursor_column());
1239 refresh();
1240 curs_set(1);
1241 } else {
1242 if (cursor_x >= 0) {
1243 move(cursor_y, cursor_x);
1244 } else {
1245 move(LINES - 1, 0);
1247 refresh();
1249 /* visible cursor is useful for screen readers */
1250 if (show_cursor) {
1251 curs_set(1);
1252 } else {
1253 curs_set(0);
1258 void update_titleline(void)
1260 curs_set(0);
1261 do_update_titleline();
1262 post_update();
1265 void update_full(void)
1267 if (!ui_initialized)
1268 return;
1270 curs_set(0);
1272 do_update_view(1);
1273 do_update_titleline();
1274 do_update_statusline();
1275 do_update_commandline();
1277 post_update();
1280 static void update_commandline(void)
1282 curs_set(0);
1283 do_update_commandline();
1284 post_update();
1287 void update_statusline(void)
1289 if (!ui_initialized)
1290 return;
1292 curs_set(0);
1293 do_update_statusline();
1294 post_update();
1297 void info_msg(const char *format, ...)
1299 va_list ap;
1301 va_start(ap, format);
1302 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1303 va_end(ap);
1305 msg_is_error = 0;
1307 update_commandline();
1310 void error_msg(const char *format, ...)
1312 va_list ap;
1314 strcpy(error_buf, "Error: ");
1315 va_start(ap, format);
1316 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1317 va_end(ap);
1319 d_print("%s\n", error_buf);
1321 msg_is_error = 1;
1322 error_count++;
1324 if (ui_initialized) {
1325 error_time = time(NULL);
1326 update_commandline();
1327 } else {
1328 warn("%s\n", error_buf);
1329 error_buf[0] = 0;
1333 int yes_no_query(const char *format, ...)
1335 char buffer[512];
1336 va_list ap;
1337 int ret = 0;
1339 va_start(ap, format);
1340 vsnprintf(buffer, sizeof(buffer), format, ap);
1341 va_end(ap);
1343 move(LINES - 1, 0);
1344 bkgdset(pairs[CURSED_INFO]);
1346 /* no need to convert buffer.
1347 * it is always encoded in the right charset (assuming filenames are
1348 * encoded in same charset as LC_CTYPE).
1351 addstr(buffer);
1352 clrtoeol();
1353 refresh();
1355 while (1) {
1356 int ch = getch();
1358 if (ch == ERR || ch == 0)
1359 continue;
1360 if (ch == 'y')
1361 ret = 1;
1362 break;
1364 update_commandline();
1365 return ret;
1368 void search_not_found(void)
1370 const char *what = "Track";
1372 if (search_restricted) {
1373 switch (cur_view) {
1374 case TREE_VIEW:
1375 what = "Artist/album";
1376 break;
1377 case SORTED_VIEW:
1378 case PLAYLIST_VIEW:
1379 case QUEUE_VIEW:
1380 what = "Title";
1381 break;
1382 case BROWSER_VIEW:
1383 what = "File/Directory";
1384 break;
1385 case FILTERS_VIEW:
1386 what = "Filter";
1387 break;
1388 case HELP_VIEW:
1389 what = "Binding/command/option";
1390 break;
1392 } else {
1393 switch (cur_view) {
1394 case TREE_VIEW:
1395 case SORTED_VIEW:
1396 case PLAYLIST_VIEW:
1397 case QUEUE_VIEW:
1398 what = "Track";
1399 break;
1400 case BROWSER_VIEW:
1401 what = "File/Directory";
1402 break;
1403 case FILTERS_VIEW:
1404 what = "Filter";
1405 break;
1406 case HELP_VIEW:
1407 what = "Binding/command/option";
1408 break;
1411 info_msg("%s not found: %s", what, search_str ? : "");
1414 void set_view(int view)
1416 if (view == cur_view)
1417 return;
1419 cur_view = view;
1420 switch (cur_view) {
1421 case TREE_VIEW:
1422 searchable = tree_searchable;
1423 break;
1424 case SORTED_VIEW:
1425 searchable = lib_editable.searchable;
1426 break;
1427 case PLAYLIST_VIEW:
1428 searchable = pl_editable.searchable;
1429 break;
1430 case QUEUE_VIEW:
1431 searchable = pq_editable.searchable;
1432 break;
1433 case BROWSER_VIEW:
1434 searchable = browser_searchable;
1435 break;
1436 case FILTERS_VIEW:
1437 searchable = filters_searchable;
1438 break;
1439 case HELP_VIEW:
1440 searchable = help_searchable;
1441 update_help_window();
1442 break;
1445 curs_set(0);
1446 do_update_view(1);
1447 post_update();
1450 void enter_command_mode(void)
1452 error_buf[0] = 0;
1453 error_time = 0;
1454 input_mode = COMMAND_MODE;
1455 update_commandline();
1458 void enter_search_mode(void)
1460 error_buf[0] = 0;
1461 error_time = 0;
1462 input_mode = SEARCH_MODE;
1463 search_direction = SEARCH_FORWARD;
1464 update_commandline();
1467 void enter_search_backward_mode(void)
1469 error_buf[0] = 0;
1470 error_time = 0;
1471 input_mode = SEARCH_MODE;
1472 search_direction = SEARCH_BACKWARD;
1473 update_commandline();
1476 void update_colors(void)
1478 int i;
1480 if (!ui_initialized)
1481 return;
1483 for (i = 0; i < NR_CURSED; i++) {
1484 int bg = colors[cursed_to_bg_idx[i]];
1485 int fg = colors[cursed_to_fg_idx[i]];
1486 int pair = i + 1;
1488 if (fg >= 8 && fg <= 15) {
1489 /* fg colors 8..15 are special (0..7 + bold) */
1490 init_pair(pair, fg & 7, bg);
1491 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1492 } else {
1493 init_pair(pair, fg, bg);
1494 pairs[i] = COLOR_PAIR(pair);
1499 static void clear_error(void)
1501 time_t t = time(NULL);
1503 /* prevent accidental clearing of error messages */
1504 if (t - error_time < 2)
1505 return;
1507 if (error_buf[0]) {
1508 error_time = 0;
1509 error_buf[0] = 0;
1510 update_commandline();
1514 /* screen updates }}} */
1516 static void spawn_status_program(void)
1518 static const char *status_strs[] = { "stopped", "playing", "paused" };
1519 const char *stream_title = NULL;
1520 char *argv[32];
1521 int i, status;
1523 if (status_display_program == NULL || status_display_program[0] == 0)
1524 return;
1526 player_info_lock();
1527 status = player_info.status;
1528 if (status == 1)
1529 stream_title = get_stream_title();
1531 i = 0;
1532 argv[i++] = xstrdup(status_display_program);
1534 argv[i++] = xstrdup("status");
1535 argv[i++] = xstrdup(status_strs[status]);
1536 if (player_info.ti) {
1537 static const char *keys[] = {
1538 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1540 int j;
1542 if (is_url(player_info.ti->filename)) {
1543 argv[i++] = xstrdup("url");
1544 argv[i++] = xstrdup(player_info.ti->filename);
1545 if (stream_title) {
1546 argv[i++] = xstrdup("title");
1547 argv[i++] = xstrdup(stream_title);
1549 } else {
1550 char buf[32];
1552 argv[i++] = xstrdup("file");
1553 argv[i++] = xstrdup(player_info.ti->filename);
1554 for (j = 0; keys[j]; j++) {
1555 const char *key = keys[j];
1556 const char *val;
1558 val = keyvals_get_val(player_info.ti->comments, key);
1559 if (val) {
1560 argv[i++] = xstrdup(key);
1561 argv[i++] = xstrdup(val);
1564 snprintf(buf, sizeof(buf), "%d", player_info.ti->duration);
1565 argv[i++] = xstrdup("duration");
1566 argv[i++] = xstrdup(buf);
1569 argv[i++] = NULL;
1570 player_info_unlock();
1572 if (spawn(argv, &status) == -1)
1573 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1574 for (i = 0; argv[i]; i++)
1575 free(argv[i]);
1578 static int ctrl_c_pressed = 0;
1580 static void sig_int(int sig)
1582 ctrl_c_pressed = 1;
1585 static void sig_hup(int sig)
1587 cmus_running = 0;
1590 static int needs_to_resize = 1;
1592 static void sig_winch(int sig)
1594 needs_to_resize = 1;
1597 static int get_window_size(int *lines, int *columns)
1599 struct winsize ws;
1601 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1602 return -1;
1603 *columns = ws.ws_col;
1604 *lines = ws.ws_row;
1605 return 0;
1608 static void resize_tree_view(int w, int h)
1610 tree_win_w = w / 3;
1611 track_win_w = w - tree_win_w - 1;
1612 if (tree_win_w < 8)
1613 tree_win_w = 8;
1614 if (track_win_w < 8)
1615 track_win_w = 8;
1616 tree_win_x = 0;
1617 tree_win_y = 0;
1618 track_win_x = tree_win_w + 1;
1619 track_win_y = 0;
1621 h--;
1622 window_set_nr_rows(lib_tree_win, h);
1623 window_set_nr_rows(lib_track_win, h);
1626 static void update(void)
1628 int needs_view_update = 0;
1629 int needs_title_update = 0;
1630 int needs_status_update = 0;
1631 int needs_command_update = 0;
1632 int needs_spawn = 0;
1634 if (needs_to_resize) {
1635 int w, h;
1636 int columns, lines;
1638 if (get_window_size(&lines, &columns) == 0) {
1639 needs_to_resize = 0;
1640 resizeterm(lines, columns);
1641 w = COLS;
1642 h = LINES - 3;
1643 if (w < 16)
1644 w = 16;
1645 if (h < 8)
1646 h = 8;
1647 editable_lock();
1648 resize_tree_view(w, h);
1649 window_set_nr_rows(lib_editable.win, h - 1);
1650 window_set_nr_rows(pl_editable.win, h - 1);
1651 window_set_nr_rows(pq_editable.win, h - 1);
1652 window_set_nr_rows(filters_win, h - 1);
1653 window_set_nr_rows(help_win, h - 1);
1654 window_set_nr_rows(browser_win, h - 1);
1655 editable_unlock();
1656 needs_title_update = 1;
1657 needs_status_update = 1;
1658 needs_command_update = 1;
1662 player_info_lock();
1663 editable_lock();
1665 needs_spawn = player_info.status_changed || player_info.file_changed ||
1666 player_info.metadata_changed;
1668 if (player_info.file_changed) {
1669 player_info.file_changed = 0;
1670 needs_title_update = 1;
1671 needs_status_update = 1;
1673 if (player_info.metadata_changed) {
1674 player_info.metadata_changed = 0;
1675 needs_title_update = 1;
1677 if (player_info.position_changed || player_info.status_changed) {
1678 player_info.position_changed = 0;
1679 player_info.status_changed = 0;
1681 needs_status_update = 1;
1683 switch (cur_view) {
1684 case TREE_VIEW:
1685 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
1686 break;
1687 case SORTED_VIEW:
1688 needs_view_update += lib_editable.win->changed;
1689 break;
1690 case PLAYLIST_VIEW:
1691 needs_view_update += pl_editable.win->changed;
1692 break;
1693 case QUEUE_VIEW:
1694 needs_view_update += pq_editable.win->changed;
1695 break;
1696 case BROWSER_VIEW:
1697 needs_view_update += browser_win->changed;
1698 break;
1699 case FILTERS_VIEW:
1700 needs_view_update += filters_win->changed;
1701 break;
1702 case HELP_VIEW:
1703 needs_view_update += help_win->changed;
1704 break;
1707 /* total time changed? */
1708 if (play_library) {
1709 needs_status_update += lib_editable.win->changed;
1710 lib_editable.win->changed = 0;
1711 } else {
1712 needs_status_update += pl_editable.win->changed;
1713 lib_editable.win->changed = 0;
1716 editable_unlock();
1717 player_info_unlock();
1719 if (needs_spawn)
1720 spawn_status_program();
1722 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1723 curs_set(0);
1725 if (needs_view_update)
1726 do_update_view(0);
1727 if (needs_title_update)
1728 do_update_titleline();
1729 if (needs_status_update)
1730 do_update_statusline();
1731 if (needs_command_update)
1732 do_update_commandline();
1733 post_update();
1737 static void handle_ch(uchar ch)
1739 clear_error();
1740 if (input_mode == NORMAL_MODE) {
1741 normal_mode_ch(ch);
1742 } else if (input_mode == COMMAND_MODE) {
1743 command_mode_ch(ch);
1744 update_commandline();
1745 } else if (input_mode == SEARCH_MODE) {
1746 search_mode_ch(ch);
1747 update_commandline();
1751 static void handle_key(int key)
1753 clear_error();
1754 if (input_mode == NORMAL_MODE) {
1755 normal_mode_key(key);
1756 } else if (input_mode == COMMAND_MODE) {
1757 command_mode_key(key);
1758 update_commandline();
1759 } else if (input_mode == SEARCH_MODE) {
1760 search_mode_key(key);
1761 update_commandline();
1765 static void u_getch(void)
1767 int key;
1768 int bit = 7;
1769 int mask = (1 << 7);
1770 uchar u, ch;
1772 key = getch();
1773 if (key == ERR || key == 0)
1774 return;
1776 if (key > 255) {
1777 handle_key(key);
1778 return;
1781 ch = (unsigned char)key;
1782 while (bit > 0 && ch & mask) {
1783 mask >>= 1;
1784 bit--;
1786 if (bit == 7) {
1787 /* ascii */
1788 u = ch;
1789 } else {
1790 int count;
1792 u = ch & ((1 << bit) - 1);
1793 count = 6 - bit;
1794 while (count) {
1795 key = getch();
1796 if (key == ERR || key == 0)
1797 return;
1799 ch = (unsigned char)key;
1800 u = (u << 6) | (ch & 63);
1801 count--;
1804 handle_ch(u);
1807 static void main_loop(void)
1809 int rc, fd_high;
1811 fd_high = server_socket;
1812 while (cmus_running) {
1813 fd_set set;
1814 struct timeval tv;
1815 int poll_mixer = 0;
1816 int i, nr_fds = 0;
1817 int fds[NR_MIXER_FDS];
1818 struct list_head *item;
1819 struct client *client;
1821 update();
1823 /* Timeout must be so small that screen updates seem instant.
1824 * Only affects changes done in other threads (worker, player).
1826 * Too small timeout makes window updates too fast (wastes CPU).
1828 * Too large timeout makes status line (position) updates too slow.
1829 * The timeout is accuracy of player position.
1831 tv.tv_sec = 0;
1832 tv.tv_usec = 0;
1834 player_info_lock();
1835 if (player_info.status == PLAYER_STATUS_PLAYING) {
1836 // player position updates need to be fast
1837 tv.tv_usec = 100e3;
1839 player_info_unlock();
1841 if (!tv.tv_usec && worker_has_job(JOB_TYPE_ANY)) {
1842 // playlist is loading. screen needs to be updated
1843 tv.tv_usec = 250e3;
1846 FD_ZERO(&set);
1847 FD_SET(0, &set);
1848 FD_SET(server_socket, &set);
1849 list_for_each_entry(client, &client_head, node) {
1850 FD_SET(client->fd, &set);
1851 if (client->fd > fd_high)
1852 fd_high = client->fd;
1854 if (!soft_vol) {
1855 nr_fds = mixer_get_fds(fds);
1856 if (nr_fds == -OP_ERROR_NOT_SUPPORTED) {
1857 // mixer has no pollable file descriptors
1858 poll_mixer = 1;
1859 if (!tv.tv_usec)
1860 tv.tv_usec = 500e3;
1862 for (i = 0; i < nr_fds; i++) {
1863 BUG_ON(fds[i] <= 0);
1864 FD_SET(fds[i], &set);
1865 if (fds[i] > fd_high)
1866 fd_high = fds[i];
1870 if (tv.tv_usec) {
1871 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1872 } else {
1873 rc = select(fd_high + 1, &set, NULL, NULL, NULL);
1875 if (poll_mixer) {
1876 int ol = volume_l;
1877 int or = volume_r;
1879 mixer_read_volume();
1880 if (ol != volume_l || or != volume_r)
1881 update_statusline();
1884 if (rc <= 0) {
1885 if (ctrl_c_pressed) {
1886 handle_ch(0x03);
1887 ctrl_c_pressed = 0;
1890 continue;
1893 for (i = 0; i < nr_fds; i++) {
1894 if (FD_ISSET(fds[i], &set)) {
1895 d_print("vol changed\n");
1896 mixer_read_volume();
1897 update_statusline();
1900 if (FD_ISSET(server_socket, &set))
1901 server_accept();
1903 // server_serve() can remove client from the list
1904 item = client_head.next;
1905 while (item != &client_head) {
1906 struct list_head *next = item->next;
1907 client = container_of(item, struct client, node);
1908 if (FD_ISSET(client->fd, &set))
1909 server_serve(client);
1910 item = next;
1913 if (FD_ISSET(0, &set)) {
1914 if (using_utf8) {
1915 u_getch();
1916 } else {
1917 int key = getch();
1919 if (key != ERR && key != 0) {
1920 if (key > 255) {
1921 handle_key(key);
1922 } else {
1923 uchar ch = key;
1925 if (ch > 0x7f)
1926 ch |= U_INVALID_MASK;
1927 handle_ch(ch);
1935 static int get_next(struct track_info **ti)
1937 struct track_info *info;
1939 editable_lock();
1940 info = play_queue_remove();
1941 if (info == NULL) {
1942 if (play_library) {
1943 info = lib_set_next();
1944 } else {
1945 info = pl_set_next();
1948 editable_unlock();
1950 if (info == NULL)
1951 return -1;
1953 *ti = info;
1954 return 0;
1957 static const struct player_callbacks player_callbacks = {
1958 .get_next = get_next
1961 static void init_curses(void)
1963 struct sigaction act;
1964 char *ptr, *term;
1966 sigemptyset(&act.sa_mask);
1967 act.sa_flags = 0;
1968 act.sa_handler = sig_int;
1969 sigaction(SIGINT, &act, NULL);
1971 sigemptyset(&act.sa_mask);
1972 act.sa_flags = 0;
1973 act.sa_handler = sig_hup;
1974 sigaction(SIGHUP, &act, NULL);
1976 sigemptyset(&act.sa_mask);
1977 act.sa_flags = 0;
1978 act.sa_handler = SIG_IGN;
1979 sigaction(SIGPIPE, &act, NULL);
1981 sigemptyset(&act.sa_mask);
1982 act.sa_flags = 0;
1983 act.sa_handler = sig_winch;
1984 sigaction(SIGWINCH, &act, NULL);
1986 initscr();
1988 /* turn off kb buffering */
1989 cbreak();
1991 keypad(stdscr, TRUE);
1993 /* wait max 5 * 0.1 s if there are no keys available
1994 * doesn't really matter because we use select()
1996 halfdelay(5);
1998 noecho();
1999 if (has_colors()) {
2000 start_color();
2001 use_default_colors();
2003 d_print("Number of supported colors: %d\n", COLORS);
2004 ui_initialized = 1;
2006 /* this was disabled while initializing because it needs to be
2007 * called only once after all colors have been set
2009 update_colors();
2011 ptr = print_buffer;
2012 t_ts = tgetstr("ts", &ptr);
2013 t_fs = tgetstr("fs", &ptr);
2014 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
2016 if (!t_fs)
2017 t_ts = NULL;
2019 term = getenv("TERM");
2020 if (!t_ts && term) {
2022 * Eterm: Eterm
2023 * aterm: rxvt
2024 * mlterm: xterm
2025 * terminal (xfce): xterm
2026 * urxvt: rxvt-unicode
2027 * xterm: xterm, xterm-{,16,88,256}color
2029 if (!strcmp(term, "screen")) {
2030 t_ts = "\033_";
2031 t_fs = "\033\\";
2032 } else if (!strncmp(term, "xterm", 5) ||
2033 !strncmp(term, "rxvt", 4) ||
2034 !strcmp(term, "Eterm")) {
2035 /* \033]1; change icon
2036 * \033]2; change title
2037 * \033]0; change both
2039 t_ts = "\033]0;";
2040 t_fs = "\007";
2045 static void init_all(void)
2047 server_init(server_address);
2049 /* does not select output plugin */
2050 player_init(&player_callbacks);
2052 /* plugins have been loaded so we know what plugin options are available */
2053 options_add();
2055 lib_init();
2056 searchable = tree_searchable;
2057 pl_init();
2058 cmus_init();
2059 browser_init();
2060 filters_init();
2061 help_init();
2062 cmdline_init();
2063 commands_init();
2064 search_mode_init();
2066 /* almost everything must be initialized now */
2067 options_load();
2069 /* finally we can set the output plugin */
2070 player_set_op(output_plugin);
2071 if (!soft_vol)
2072 mixer_open();
2074 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
2075 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
2076 pl_filename = xstrdup(pl_autosave_filename);
2077 lib_filename = xstrdup(lib_autosave_filename);
2079 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
2080 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
2082 if (error_count) {
2083 char buf[16];
2085 warn("Press <enter> to continue.");
2086 fgets(buf, sizeof(buf), stdin);
2088 help_add_all_unbound();
2090 init_curses();
2093 static void exit_all(void)
2095 endwin();
2097 options_exit();
2099 server_exit();
2100 cmus_exit();
2101 cmus_save(lib_for_each, lib_autosave_filename);
2102 cmus_save(pl_for_each, pl_autosave_filename);
2104 player_exit();
2105 op_exit_plugins();
2106 commands_exit();
2107 search_mode_exit();
2108 filters_exit();
2109 help_exit();
2110 browser_exit();
2113 enum {
2114 FLAG_LISTEN,
2115 FLAG_PLUGINS,
2116 FLAG_SHOW_CURSOR,
2117 FLAG_HELP,
2118 FLAG_VERSION,
2119 NR_FLAGS
2122 static struct option options[NR_FLAGS + 1] = {
2123 { 0, "listen", 1 },
2124 { 0, "plugins", 0 },
2125 { 0, "show-cursor", 0 },
2126 { 0, "help", 0 },
2127 { 0, "version", 0 },
2128 { 0, NULL, 0 }
2131 static const char *usage =
2132 "Usage: %s [OPTION]...\n"
2133 "Curses based music player.\n"
2134 "\n"
2135 " --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
2136 " ADDR is either a UNIX socket or host[:port]\n"
2137 " WARNING: using TCP/IP is insecure!\n"
2138 " --plugins list available plugins and exit\n"
2139 " --show-cursor always visible cursor\n"
2140 " --help display this help and exit\n"
2141 " --version " VERSION "\n"
2142 "\n"
2143 "Use cmus-remote to control cmus from command line.\n"
2144 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2146 int main(int argc, char *argv[])
2148 int list_plugins = 0;
2150 program_name = argv[0];
2151 argv++;
2152 while (1) {
2153 int idx;
2154 char *arg;
2156 idx = get_option(&argv, options, &arg);
2157 if (idx < 0)
2158 break;
2160 switch (idx) {
2161 case FLAG_HELP:
2162 printf(usage, program_name);
2163 return 0;
2164 case FLAG_VERSION:
2165 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
2166 return 0;
2167 case FLAG_PLUGINS:
2168 list_plugins = 1;
2169 break;
2170 case FLAG_LISTEN:
2171 server_address = xstrdup(arg);
2172 break;
2173 case FLAG_SHOW_CURSOR:
2174 show_cursor = 1;
2175 break;
2179 setlocale(LC_CTYPE, "");
2180 #ifdef CODESET
2181 charset = nl_langinfo(CODESET);
2182 #else
2183 charset = "ISO-8859-1";
2184 #endif
2185 if (strcmp(charset, "UTF-8") == 0) {
2186 using_utf8 = 1;
2187 } else {
2188 using_utf8 = 0;
2190 misc_init();
2191 if (server_address == NULL)
2192 server_address = xstrjoin(cmus_config_dir, "/socket");
2193 debug_init();
2194 d_print("charset = '%s'\n", charset);
2196 ip_load_plugins();
2197 op_load_plugins();
2198 if (list_plugins) {
2199 ip_dump_plugins();
2200 op_dump_plugins();
2201 return 0;
2203 init_all();
2204 main_loop();
2205 exit_all();
2206 return 0;