Small clean up to http_open()
[cmus.git] / ui_curses.c
blob538e8587c51af62b17ea9c81704d89099d39c668
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"
48 #include <unistd.h>
49 #include <stdlib.h>
50 #include <stdio.h>
51 #include <errno.h>
52 #include <sys/ioctl.h>
53 #include <ctype.h>
54 #include <dirent.h>
55 #include <locale.h>
56 #include <langinfo.h>
57 #include <iconv.h>
58 #include <signal.h>
59 #include <stdarg.h>
61 #if defined(__sun__) || defined(__CYGWIN__)
62 /* TIOCGWINSZ */
63 #include <termios.h>
64 #include <ncurses.h>
65 #else
66 #include <curses.h>
67 #endif
69 /* defined in <term.h> but without const */
70 char *tgetstr(const char *id, char **area);
71 char *tgoto(const char *cap, int col, int row);
73 /* globals. documented in ui_curses.h */
75 int cmus_running = 1;
76 int ui_initialized = 0;
77 enum ui_input_mode input_mode = NORMAL_MODE;
78 int cur_view = TREE_VIEW;
79 struct searchable *searchable;
80 char *lib_filename = NULL;
81 char *pl_filename = NULL;
83 /* ------------------------------------------------------------------------- */
85 static char *lib_autosave_filename;
86 static char *pl_autosave_filename;
88 /* shown error message and time stamp
89 * error is cleared if it is older than 3s and key was pressed
91 static char error_buf[512];
92 static time_t error_time = 0;
93 /* info messages are displayed in different color */
94 static int msg_is_error;
95 static int error_count = 0;
97 static char *server_address = NULL;
99 static char *charset = NULL;
100 static char print_buffer[512];
102 /* destination buffer for utf8_encode and utf8_decode */
103 static char conv_buffer[512];
105 #define print_buffer_size (sizeof(print_buffer) - 1)
106 static int using_utf8;
108 static const char *t_ts;
109 static const char *t_fs;
111 static int tree_win_x = 0;
112 static int tree_win_y = 0;
113 static int tree_win_w = 0;
115 static int track_win_x = 0;
116 static int track_win_y = 0;
117 static int track_win_w = 0;
119 static int cursor_x;
120 static int cursor_y;
122 enum {
123 CURSED_WIN,
124 CURSED_WIN_CUR,
125 CURSED_WIN_SEL,
126 CURSED_WIN_SEL_CUR,
128 CURSED_WIN_ACTIVE,
129 CURSED_WIN_ACTIVE_CUR,
130 CURSED_WIN_ACTIVE_SEL,
131 CURSED_WIN_ACTIVE_SEL_CUR,
133 CURSED_SEPARATOR,
134 CURSED_WIN_TITLE,
135 CURSED_COMMANDLINE,
136 CURSED_STATUSLINE,
138 CURSED_TITLELINE,
139 CURSED_DIR,
140 CURSED_ERROR,
141 CURSED_INFO,
143 NR_CURSED
146 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
147 COLOR_WIN_BG,
148 COLOR_WIN_BG,
149 COLOR_WIN_INACTIVE_SEL_BG,
150 COLOR_WIN_INACTIVE_CUR_SEL_BG,
152 COLOR_WIN_BG,
153 COLOR_WIN_BG,
154 COLOR_WIN_SEL_BG,
155 COLOR_WIN_CUR_SEL_BG,
157 COLOR_WIN_BG,
158 COLOR_WIN_TITLE_BG,
159 COLOR_CMDLINE_BG,
160 COLOR_STATUSLINE_BG,
162 COLOR_TITLELINE_BG,
163 COLOR_WIN_BG,
164 COLOR_CMDLINE_BG,
165 COLOR_CMDLINE_BG
168 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
169 COLOR_WIN_FG,
170 COLOR_WIN_CUR,
171 COLOR_WIN_INACTIVE_SEL_FG,
172 COLOR_WIN_INACTIVE_CUR_SEL_FG,
174 COLOR_WIN_FG,
175 COLOR_WIN_CUR,
176 COLOR_WIN_SEL_FG,
177 COLOR_WIN_CUR_SEL_FG,
179 COLOR_SEPARATOR,
180 COLOR_WIN_TITLE_FG,
181 COLOR_CMDLINE_FG,
182 COLOR_STATUSLINE_FG,
184 COLOR_TITLELINE_FG,
185 COLOR_WIN_DIR,
186 COLOR_ERROR,
187 COLOR_INFO
190 /* index is CURSED_*, value is fucking color pair */
191 static int pairs[NR_CURSED];
193 enum {
194 TF_ARTIST,
195 TF_ALBUM,
196 TF_DISC,
197 TF_TRACK,
198 TF_TITLE,
199 TF_YEAR,
200 TF_GENRE,
201 TF_DURATION,
202 TF_PATHFILE,
203 TF_FILE,
204 NR_TFS
207 static struct format_option track_fopts[NR_TFS + 1] = {
208 DEF_FO_STR('a'),
209 DEF_FO_STR('l'),
210 DEF_FO_INT('D'),
211 DEF_FO_INT('n'),
212 DEF_FO_STR('t'),
213 DEF_FO_STR('y'),
214 DEF_FO_STR('g'),
215 DEF_FO_TIME('d'),
216 DEF_FO_STR('f'),
217 DEF_FO_STR('F'),
218 DEF_FO_END
221 enum {
222 SF_STATUS,
223 SF_POSITION,
224 SF_DURATION,
225 SF_TOTAL,
226 SF_VOLUME,
227 SF_LVOLUME,
228 SF_RVOLUME,
229 SF_BUFFER,
230 SF_REPEAT,
231 SF_CONTINUE,
232 SF_SHUFFLE,
233 SF_PLAYLISTMODE,
234 NR_SFS
237 static struct format_option status_fopts[NR_SFS + 1] = {
238 DEF_FO_STR('s'),
239 DEF_FO_TIME('p'),
240 DEF_FO_TIME('d'),
241 DEF_FO_TIME('t'),
242 DEF_FO_INT('v'),
243 DEF_FO_INT('l'),
244 DEF_FO_INT('r'),
245 DEF_FO_INT('b'),
246 DEF_FO_STR('R'),
247 DEF_FO_STR('C'),
248 DEF_FO_STR('S'),
249 DEF_FO_STR('L'),
250 DEF_FO_END
253 static void utf8_encode(const char *buffer)
255 static iconv_t cd = (iconv_t)-1;
256 size_t is, os;
257 const char *i;
258 char *o;
259 int rc;
261 if (cd == (iconv_t)-1) {
262 d_print("iconv_open(UTF-8, %s)\n", charset);
263 cd = iconv_open("UTF-8", charset);
264 if (cd == (iconv_t)-1) {
265 d_print("iconv_open failed: %s\n", strerror(errno));
266 return;
269 i = buffer;
270 o = conv_buffer;
271 is = strlen(i);
272 os = sizeof(conv_buffer) - 1;
273 rc = iconv(cd, (void *)&i, &is, &o, &os);
274 *o = 0;
275 if (rc == -1) {
276 d_print("iconv failed: %s\n", strerror(errno));
277 return;
281 static void utf8_decode(const char *buffer)
283 static iconv_t cd = (iconv_t)-1;
284 size_t is, os;
285 const char *i;
286 char *o;
287 int rc;
289 if (cd == (iconv_t)-1) {
290 d_print("iconv_open(%s, UTF-8)\n", charset);
291 cd = iconv_open(charset, "UTF-8");
292 if (cd == (iconv_t)-1) {
293 d_print("iconv_open failed: %s\n", strerror(errno));
294 return;
297 i = buffer;
298 o = conv_buffer;
299 is = strlen(i);
300 os = sizeof(conv_buffer) - 1;
301 rc = iconv(cd, (void *)&i, &is, &o, &os);
302 *o = 0;
303 if (rc == -1) {
304 d_print("iconv failed: %s\n", strerror(errno));
305 return;
309 /* screen updates {{{ */
311 static void dump_print_buffer(int row, int col)
313 if (using_utf8) {
314 mvaddstr(row, col, print_buffer);
315 } else {
316 utf8_decode(print_buffer);
317 mvaddstr(row, col, conv_buffer);
321 /* print @str into @buf
323 * if @str is shorter than @width pad with spaces
324 * if @str is wider than @width truncate and add "..."
326 static int format_str(char *buf, const char *str, int width)
328 int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;
330 while (1) {
331 uchar u;
332 int w;
334 u_get_char(str, &s, &u);
335 if (u == 0) {
336 memset(buf + d, ' ', width);
337 d += width;
338 break;
341 w = u_char_width(u);
342 if (width == 3)
343 ellipsis_pos = d;
344 if (width == 4 && w == 2) {
345 /* can't cut double-width char */
346 ellipsis_pos = d + 1;
347 cut_double_width = 1;
350 width -= w;
351 if (width < 0) {
352 /* does not fit */
353 d = ellipsis_pos;
354 if (cut_double_width) {
355 /* first half of the double-width char */
356 buf[d - 1] = ' ';
358 buf[d++] = '.';
359 buf[d++] = '.';
360 buf[d++] = '.';
361 break;
363 u_set_char(buf, &d, u);
365 return d;
368 static void sprint(int row, int col, const char *str, int width)
370 int pos = 0;
372 print_buffer[pos++] = ' ';
373 pos += format_str(print_buffer + pos, str, width - 2);
374 print_buffer[pos++] = ' ';
375 print_buffer[pos] = 0;
376 dump_print_buffer(row, col);
379 static void sprint_ascii(int row, int col, const char *str, int len)
381 int l;
383 l = strlen(str);
384 len -= 2;
386 print_buffer[0] = ' ';
387 if (l > len) {
388 memcpy(print_buffer + 1, str, len - 3);
389 print_buffer[len - 2] = '.';
390 print_buffer[len - 1] = '.';
391 print_buffer[len - 0] = '.';
392 } else {
393 memcpy(print_buffer + 1, str, l);
394 memset(print_buffer + 1 + l, ' ', len - l);
396 print_buffer[len + 1] = ' ';
397 print_buffer[len + 2] = 0;
398 mvaddstr(row, col, print_buffer);
401 static void print_tree(struct window *win, int row, struct iter *iter)
403 const char *str;
404 struct artist *artist;
405 struct album *album;
406 struct iter sel;
407 int current, selected, active, pos;
409 artist = iter_to_artist(iter);
410 album = iter_to_album(iter);
411 current = 0;
412 if (lib_cur_track) {
413 if (album) {
414 current = CUR_ALBUM == album;
415 } else {
416 current = CUR_ARTIST == artist;
419 window_get_sel(win, &sel);
420 selected = iters_equal(iter, &sel);
421 active = lib_cur_win == lib_tree_win;
422 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
424 if (active && selected) {
425 cursor_x = 0;
426 cursor_y = 1 + row;
429 pos = 0;
430 print_buffer[pos++] = ' ';
431 str = artist->name;
432 if (album) {
433 print_buffer[pos++] = ' ';
434 print_buffer[pos++] = ' ';
435 str = album->name;
437 pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
438 print_buffer[pos++] = ' ';
439 print_buffer[pos++] = 0;
440 dump_print_buffer(tree_win_y + row + 1, tree_win_x);
443 static inline void fopt_set_str(struct format_option *fopt, const char *str)
445 BUG_ON(fopt->type != FO_STR);
446 if (str) {
447 fopt->fo_str = str;
448 fopt->empty = 0;
449 } else {
450 fopt->empty = 1;
454 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
456 BUG_ON(fopt->type != FO_INT);
457 fopt->fo_int = value;
458 fopt->empty = empty;
461 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
463 BUG_ON(fopt->type != FO_TIME);
464 fopt->fo_time = value;
465 fopt->empty = empty;
468 static void fill_track_fopts_track_info(struct track_info *info)
470 char *filename;
471 int num, disc;
473 if (using_utf8) {
474 filename = info->filename;
475 } else {
476 utf8_encode(info->filename);
477 filename = conv_buffer;
479 disc = comments_get_int(info->comments, "discnumber");
480 num = comments_get_int(info->comments, "tracknumber");
482 fopt_set_str(&track_fopts[TF_ARTIST], keyvals_get_val(info->comments, "artist"));
483 fopt_set_str(&track_fopts[TF_ALBUM], keyvals_get_val(info->comments, "album"));
484 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
485 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
486 fopt_set_str(&track_fopts[TF_TITLE], keyvals_get_val(info->comments, "title"));
487 fopt_set_str(&track_fopts[TF_YEAR], keyvals_get_val(info->comments, "date"));
488 fopt_set_str(&track_fopts[TF_GENRE], keyvals_get_val(info->comments, "genre"));
489 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
490 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
491 if (is_url(info->filename)) {
492 fopt_set_str(&track_fopts[TF_FILE], filename);
493 } else {
494 const char *f;
496 f = strrchr(filename, '/');
497 if (f) {
498 fopt_set_str(&track_fopts[TF_FILE], f + 1);
499 } else {
500 fopt_set_str(&track_fopts[TF_FILE], filename);
505 static void print_track(struct window *win, int row, struct iter *iter)
507 struct tree_track *track;
508 struct iter sel;
509 int current, selected, active;
511 track = iter_to_tree_track(iter);
512 current = lib_cur_track == track;
513 window_get_sel(win, &sel);
514 selected = iters_equal(iter, &sel);
515 active = lib_cur_win == lib_track_win;
516 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
518 if (active && selected) {
519 cursor_x = track_win_x;
520 cursor_y = 1 + row;
523 fill_track_fopts_track_info(tree_track_info(track));
525 if (track_info_has_tag(tree_track_info(track))) {
526 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
527 } else {
528 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
530 dump_print_buffer(track_win_y + row + 1, track_win_x);
533 /* used by print_editable only */
534 static struct simple_track *current_track;
536 static void print_editable(struct window *win, int row, struct iter *iter)
538 struct simple_track *track;
539 struct iter sel;
540 int current, selected, active;
542 track = iter_to_simple_track(iter);
543 current = current_track == track;
544 window_get_sel(win, &sel);
545 selected = iters_equal(iter, &sel);
547 if (selected) {
548 cursor_x = 0;
549 cursor_y = 1 + row;
552 active = 1;
553 if (!selected && track->marked) {
554 selected = 1;
555 active = 0;
558 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
560 fill_track_fopts_track_info(track->info);
562 if (track_info_has_tag(track->info)) {
563 format_print(print_buffer, COLS, list_win_format, track_fopts);
564 } else {
565 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
567 dump_print_buffer(row + 1, 0);
570 static void print_browser(struct window *win, int row, struct iter *iter)
572 struct browser_entry *e;
573 struct iter sel;
574 int selected;
576 e = iter_to_browser_entry(iter);
577 window_get_sel(win, &sel);
578 selected = iters_equal(iter, &sel);
579 if (selected) {
580 int active = 1;
581 int current = 0;
583 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
584 } else {
585 if (e->type == BROWSER_ENTRY_DIR) {
586 bkgdset(pairs[CURSED_DIR]);
587 } else {
588 bkgdset(pairs[CURSED_WIN]);
592 if (selected) {
593 cursor_x = 0;
594 cursor_y = 1 + row;
597 /* file name encoding == terminal encoding. no need to convert */
598 if (using_utf8) {
599 sprint(row + 1, 0, e->name, COLS);
600 } else {
601 sprint_ascii(row + 1, 0, e->name, COLS);
605 static void print_filter(struct window *win, int row, struct iter *iter)
607 char buf[256];
608 struct filter_entry *e = iter_to_filter_entry(iter);
609 struct iter sel;
610 /* window active? */
611 int active = 1;
612 /* row selected? */
613 int selected;
614 /* is the filter currently active? */
615 int current = !!e->act_stat;
616 const char stat_chars[3] = " *!";
617 int ch1, ch2, ch3, pos;
619 window_get_sel(win, &sel);
620 selected = iters_equal(iter, &sel);
621 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
623 if (selected) {
624 cursor_x = 0;
625 cursor_y = 1 + row;
628 ch1 = ' ';
629 ch3 = ' ';
630 if (e->sel_stat != e->act_stat) {
631 ch1 = '[';
632 ch3 = ']';
634 ch2 = stat_chars[e->sel_stat];
635 snprintf(buf, sizeof(buf), "%c%c%c%-15s %s", ch1, ch2, ch3, e->name, e->filter);
636 pos = format_str(print_buffer, buf, COLS - 1);
637 print_buffer[pos++] = ' ';
638 print_buffer[pos] = 0;
639 dump_print_buffer(row + 1, 0);
642 static void print_help(struct window *win, int row, struct iter *iter)
644 struct iter sel;
645 int selected;
646 int pos;
647 int active = 1;
648 char buf[512];
649 const struct help_entry *e = iter_to_help_entry(iter);
650 const struct cmus_opt *opt;
652 window_get_sel(win, &sel);
653 selected = iters_equal(iter, &sel);
654 bkgdset(pairs[(active << 2) | (selected << 1)]);
656 if (selected) {
657 cursor_x = 0;
658 cursor_y = 1 + row;
661 switch (e->type) {
662 case HE_TEXT:
663 snprintf(buf, sizeof(buf), " %s", e->text);
664 break;
665 case HE_BOUND:
666 snprintf(buf, sizeof(buf), " %-8s %-14s %s",
667 key_context_names[e->binding->ctx],
668 e->binding->key->name,
669 e->binding->cmd);
670 break;
671 case HE_UNBOUND:
672 snprintf(buf, sizeof(buf), " %s", e->command->name);
673 break;
674 case HE_OPTION:
675 opt = e->option;
676 snprintf(buf, sizeof(buf), " %-29s ", opt->name);
677 opt->get(opt->id, buf + strlen(buf));
678 break;
680 pos = format_str(print_buffer, buf, COLS - 1);
681 print_buffer[pos++] = ' ';
682 print_buffer[pos] = 0;
683 dump_print_buffer(row + 1, 0);
686 static void update_window(struct window *win, int x, int y, int w, const char *title,
687 void (*print)(struct window *, int, struct iter *))
689 struct iter iter;
690 int nr_rows;
691 int c, i;
693 win->changed = 0;
695 bkgdset(pairs[CURSED_WIN_TITLE]);
696 c = snprintf(print_buffer, w + 1, " %s", title);
697 if (c > w)
698 c = w;
699 memset(print_buffer + c, ' ', w - c + 1);
700 print_buffer[w] = 0;
701 dump_print_buffer(y, x);
702 nr_rows = window_get_nr_rows(win);
703 i = 0;
704 if (window_get_top(win, &iter)) {
705 while (i < nr_rows) {
706 print(win, i, &iter);
707 i++;
708 if (!window_get_next(win, &iter))
709 break;
713 bkgdset(pairs[0]);
714 memset(print_buffer, ' ', w);
715 print_buffer[w] = 0;
716 while (i < nr_rows) {
717 dump_print_buffer(y + i + 1, x);
718 i++;
722 static void update_tree_window(void)
724 update_window(lib_tree_win, tree_win_x, tree_win_y,
725 tree_win_w, "Artist / Album", print_tree);
728 static void update_track_window(void)
730 char title[512];
732 /* it doesn't matter what format options we use because the format
733 * string does not contain any format charaters */
734 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
735 update_window(lib_track_win, track_win_x, track_win_y,
736 track_win_w, title, print_track);
739 static const char *pretty(const char *path)
741 static int home_len = -1;
742 static char buf[256];
744 if (home_len == -1)
745 home_len = strlen(home_dir);
747 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
748 return path;
750 buf[0] = '~';
751 strcpy(buf + 1, path + home_len);
752 return buf;
755 static const char * const sorted_names[2] = { "", "sorted by " };
757 static void update_editable_window(struct editable *e, const char *title, const char *filename)
759 char buf[512];
760 int pos;
762 if (filename) {
763 if (using_utf8) {
764 /* already UTF-8 */
765 } else {
766 utf8_encode(filename);
767 filename = conv_buffer;
769 snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
770 pretty(filename), e->nr_tracks);
771 } else {
772 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
775 if (e->nr_marked) {
776 pos = strlen(buf);
777 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
779 pos = strlen(buf);
780 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
781 sorted_names[e->sort_str[0] != 0], e->sort_str);
783 update_window(e->win, 0, 0, COLS, buf, &print_editable);
786 static void update_sorted_window(void)
788 current_track = (struct simple_track *)lib_cur_track;
789 update_editable_window(&lib_editable, "Library", lib_filename);
792 static void update_pl_window(void)
794 current_track = pl_cur_track;
795 update_editable_window(&pl_editable, "Playlist", pl_filename);
798 static void update_play_queue_window(void)
800 current_track = NULL;
801 update_editable_window(&pq_editable, "Play Queue", NULL);
804 static void update_browser_window(void)
806 char title[512];
807 char *dirname;
809 if (using_utf8) {
810 /* already UTF-8 */
811 dirname = browser_dir;
812 } else {
813 utf8_encode(browser_dir);
814 dirname = conv_buffer;
816 snprintf(title, sizeof(title), "Browser - %s", dirname);
817 update_window(browser_win, 0, 0, COLS, title, print_browser);
820 static void update_filters_window(void)
822 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
825 static void update_help_window(void)
827 update_window(help_win, 0, 0, COLS, "Settings", print_help);
830 static void draw_separator(void)
832 int row;
834 bkgdset(pairs[CURSED_WIN_TITLE]);
835 mvaddch(0, tree_win_w, ' ');
836 bkgdset(pairs[CURSED_SEPARATOR]);
837 for (row = 1; row < LINES - 3; row++)
838 mvaddch(row, tree_win_w, ACS_VLINE);
841 static void do_update_view(int full)
843 cursor_x = -1;
844 cursor_y = -1;
846 switch (cur_view) {
847 case TREE_VIEW:
848 editable_lock();
849 if (full || lib_tree_win->changed)
850 update_tree_window();
851 if (full || lib_track_win->changed)
852 update_track_window();
853 editable_unlock();
854 draw_separator();
855 break;
856 case SORTED_VIEW:
857 editable_lock();
858 update_sorted_window();
859 editable_unlock();
860 break;
861 case PLAYLIST_VIEW:
862 editable_lock();
863 update_pl_window();
864 editable_unlock();
865 break;
866 case QUEUE_VIEW:
867 editable_lock();
868 update_play_queue_window();
869 editable_unlock();
870 break;
871 case BROWSER_VIEW:
872 update_browser_window();
873 break;
874 case FILTERS_VIEW:
875 update_filters_window();
876 break;
877 case HELP_VIEW:
878 update_help_window();
879 break;
883 static void do_update_statusline(void)
885 static const char *status_strs[] = { ".", ">", "|" };
886 static const char *cont_strs[] = { " ", "C" };
887 static const char *repeat_strs[] = { " ", "R" };
888 static const char *shuffle_strs[] = { " ", "S" };
889 int buffer_fill, vol, vol_left, vol_right;
890 int duration = -1;
891 char *msg;
892 char format[80];
894 editable_lock();
895 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
896 pl_editable.total_time, 0);
897 editable_unlock();
899 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
900 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
901 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
903 player_info_lock();
905 if (player_info.ti)
906 duration = player_info.ti->duration;
908 if (soft_vol) {
909 vol_left = soft_vol_l;
910 vol_right = soft_vol_r;
911 vol = (vol_left + vol_right + 1) / 2;
912 } else if (!volume_max) {
913 vol_left = vol_right = vol = -1;
914 } else {
915 vol_left = scale_to_percentage(volume_l, volume_max);
916 vol_right = scale_to_percentage(volume_r, volume_max);
917 vol = (vol_left + vol_right + 1) / 2;
919 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
921 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
923 if (show_remaining_time && duration != -1) {
924 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
925 } else {
926 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
929 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
930 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
931 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
932 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
933 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
934 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
936 strcpy(format, " %s %p ");
937 if (duration != -1)
938 strcat(format, "/ %d ");
939 strcat(format, "- %t ");
940 if (vol >= 0) {
941 if (vol_left != vol_right) {
942 strcat(format, "vol: %l,%r ");
943 } else {
944 strcat(format, "vol: %v ");
947 if (player_info.ti && is_url(player_info.ti->filename))
948 strcat(format, "buf: %b ");
949 strcat(format, "%=");
950 if (play_library) {
951 /* artist/album modes work only in lib */
952 if (shuffle) {
953 /* shuffle overrides sorted mode */
954 strcat(format, "%L from library");
955 } else if (play_sorted) {
956 strcat(format, "%L from sorted library");
957 } else {
958 strcat(format, "%L from library");
960 } else {
961 strcat(format, "playlist");
963 strcat(format, " | %1C%1R%1S ");
964 format_print(print_buffer, COLS, format, status_fopts);
966 msg = player_info.error_msg;
967 player_info.error_msg = NULL;
969 player_info_unlock();
971 bkgdset(pairs[CURSED_STATUSLINE]);
972 dump_print_buffer(LINES - 2, 0);
974 if (msg) {
975 error_msg("%s", msg);
976 free(msg);
980 static void dump_buffer(const char *buffer)
982 if (using_utf8) {
983 addstr(buffer);
984 } else {
985 utf8_decode(buffer);
986 addstr(conv_buffer);
990 static void do_update_commandline(void)
992 char *str;
993 int w, idx;
994 char ch;
996 move(LINES - 1, 0);
997 if (error_buf[0]) {
998 if (msg_is_error) {
999 bkgdset(pairs[CURSED_ERROR]);
1000 } else {
1001 bkgdset(pairs[CURSED_INFO]);
1003 addstr(error_buf);
1004 clrtoeol();
1005 return;
1007 bkgdset(pairs[CURSED_COMMANDLINE]);
1008 if (input_mode == NORMAL_MODE) {
1009 clrtoeol();
1010 return;
1013 str = cmdline.line;
1014 if (!using_utf8) {
1015 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1016 * characters are invalid UTF-8 so it really is in locale's
1017 * encoding.
1019 * This code should be safe because cmdline.bpos ==
1020 * cmdline.cpos as every non-ASCII character is counted as one
1021 * invalid UTF-8 byte.
1023 * NOTE: This has nothing to do with widths of printed
1024 * characters. I.e. even if there were control characters
1025 * (displayed as <xx>) there would be no problem because bpos
1026 * still equals to cpos, I think.
1028 utf8_encode(cmdline.line);
1029 str = conv_buffer;
1032 /* COMMAND_MODE or SEARCH_MODE */
1033 w = u_str_width(str);
1034 ch = ':';
1035 if (input_mode == SEARCH_MODE)
1036 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
1038 if (w <= COLS - 2) {
1039 addch(ch);
1040 idx = u_copy_chars(print_buffer, str, &w);
1041 print_buffer[idx] = 0;
1042 dump_buffer(print_buffer);
1043 clrtoeol();
1044 } else {
1045 /* keep cursor as far right as possible */
1046 int skip, width, cw;
1048 /* cursor pos (width, not chars. doesn't count the ':') */
1049 cw = u_str_nwidth(str, cmdline.cpos);
1051 skip = cw + 2 - COLS;
1052 if (skip > 0) {
1053 /* skip the ':' */
1054 skip--;
1056 /* skip rest (if any) */
1057 idx = u_skip_chars(str, &skip);
1059 width = COLS;
1060 idx = u_copy_chars(print_buffer, str + idx, &width);
1061 while (width < COLS) {
1062 /* cursor is at end of the buffer
1063 * print 1, 2 or 3 spaces
1065 * To clarify:
1067 * If the last _skipped_ character was double-width we may need
1068 * to print 2 spaces.
1070 * If the last _skipped_ character was invalid UTF-8 we may need
1071 * to print 3 spaces.
1073 print_buffer[idx++] = ' ';
1074 width++;
1076 print_buffer[idx] = 0;
1077 dump_buffer(print_buffer);
1078 } else {
1079 /* print ':' + COLS - 1 chars */
1080 addch(ch);
1081 width = COLS - 1;
1082 idx = u_copy_chars(print_buffer, str, &width);
1083 print_buffer[idx] = 0;
1084 dump_buffer(print_buffer);
1089 /* lock player_info! */
1090 static const char *get_stream_title(void)
1092 static char stream_title[255 * 16 + 1];
1093 char *ptr, *title;
1095 ptr = strstr(player_info.metadata, "StreamTitle='");
1096 if (ptr == NULL)
1097 return NULL;
1098 ptr += 13;
1099 title = ptr;
1100 while (*ptr) {
1101 if (*ptr == '\'' && *(ptr + 1) == ';') {
1102 memcpy(stream_title, title, ptr - title);
1103 stream_title[ptr - title] = 0;
1104 return stream_title;
1106 ptr++;
1108 return NULL;
1111 static void set_title(const char *title)
1113 if (!set_term_title)
1114 return;
1116 if (t_ts) {
1117 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1118 fflush(stdout);
1122 static void do_update_titleline(void)
1124 bkgdset(pairs[CURSED_TITLELINE]);
1125 player_info_lock();
1126 if (player_info.ti) {
1127 int i, use_alt_format = 0;
1128 char *wtitle;
1130 fill_track_fopts_track_info(player_info.ti);
1131 if (is_url(player_info.ti->filename)) {
1132 const char *title = get_stream_title();
1134 if (title == NULL)
1135 use_alt_format = 1;
1136 fopt_set_str(&track_fopts[TF_TITLE], title);
1137 } else {
1138 use_alt_format = !track_info_has_tag(player_info.ti);
1141 if (use_alt_format) {
1142 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1143 } else {
1144 format_print(print_buffer, COLS, current_format, track_fopts);
1146 dump_print_buffer(LINES - 3, 0);
1148 /* set window title */
1149 if (use_alt_format) {
1150 format_print(print_buffer, sizeof(print_buffer) - 1,
1151 window_title_alt_format, track_fopts);
1152 } else {
1153 format_print(print_buffer, sizeof(print_buffer) - 1,
1154 window_title_format, track_fopts);
1157 /* remove whitespace */
1158 i = strlen(print_buffer) - 1;
1159 while (i > 0 && print_buffer[i] == ' ')
1160 i--;
1161 print_buffer[i + 1] = 0;
1163 if (using_utf8) {
1164 wtitle = print_buffer;
1165 } else {
1166 utf8_decode(print_buffer);
1167 wtitle = conv_buffer;
1170 set_title(wtitle);
1171 } else {
1172 move(LINES - 3, 0);
1173 clrtoeol();
1175 set_title("cmus " VERSION);
1177 player_info_unlock();
1180 static int cmdline_cursor_column(void)
1182 char *str;
1183 int cw, skip, s;
1185 str = cmdline.line;
1186 if (!using_utf8) {
1187 /* see do_update_commandline */
1188 utf8_encode(cmdline.line);
1189 str = conv_buffer;
1192 /* width of the text in the buffer before cursor */
1193 cw = u_str_nwidth(str, cmdline.cpos);
1195 if (1 + cw < COLS) {
1196 /* whole line is visible */
1197 return 1 + cw;
1200 /* beginning of cmdline is not visible */
1202 /* check if the first visible char in cmdline would be halved
1203 * double-width character (or invalid byte <xx>) which is not possible.
1204 * we need to skip the whole character and move cursor to COLS - 2
1205 * column. */
1206 skip = cw + 2 - COLS;
1208 /* skip the ':' */
1209 skip--;
1211 /* skip rest */
1212 s = skip;
1213 u_skip_chars(str, &s);
1214 if (s > skip) {
1215 /* the last skipped char was double-width or <xx> */
1216 return COLS - 1 - (s - skip);
1218 return COLS - 1;
1221 static void post_update(void)
1223 /* refresh makes cursor visible at least for urxvt */
1224 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1225 move(LINES - 1, cmdline_cursor_column());
1226 refresh();
1227 curs_set(1);
1228 } else {
1229 if (cursor_x >= 0) {
1230 move(cursor_y, cursor_x);
1231 } else {
1232 move(LINES - 1, 0);
1234 refresh();
1235 curs_set(0);
1239 void update_titleline(void)
1241 curs_set(0);
1242 do_update_titleline();
1243 post_update();
1246 void update_full(void)
1248 if (!ui_initialized)
1249 return;
1251 curs_set(0);
1253 do_update_view(1);
1254 do_update_titleline();
1255 do_update_statusline();
1256 do_update_commandline();
1258 post_update();
1261 static void update_commandline(void)
1263 curs_set(0);
1264 do_update_commandline();
1265 post_update();
1268 void update_statusline(void)
1270 if (!ui_initialized)
1271 return;
1273 curs_set(0);
1274 do_update_statusline();
1275 post_update();
1278 void info_msg(const char *format, ...)
1280 va_list ap;
1282 va_start(ap, format);
1283 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1284 va_end(ap);
1286 msg_is_error = 0;
1288 update_commandline();
1291 void error_msg(const char *format, ...)
1293 va_list ap;
1295 strcpy(error_buf, "Error: ");
1296 va_start(ap, format);
1297 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1298 va_end(ap);
1300 d_print("%s\n", error_buf);
1302 msg_is_error = 1;
1303 error_count++;
1305 if (ui_initialized) {
1306 error_time = time(NULL);
1307 update_commandline();
1308 } else {
1309 warn("%s\n", error_buf);
1310 error_buf[0] = 0;
1314 int yes_no_query(const char *format, ...)
1316 char buffer[512];
1317 va_list ap;
1318 int ret = 0;
1320 va_start(ap, format);
1321 vsnprintf(buffer, sizeof(buffer), format, ap);
1322 va_end(ap);
1324 move(LINES - 1, 0);
1325 bkgdset(pairs[CURSED_INFO]);
1327 /* no need to convert buffer.
1328 * it is always encoded in the right charset (assuming filenames are
1329 * encoded in same charset as LC_CTYPE).
1332 addstr(buffer);
1333 clrtoeol();
1334 refresh();
1336 while (1) {
1337 int ch = getch();
1339 if (ch == ERR || ch == 0)
1340 continue;
1341 if (ch == 'y')
1342 ret = 1;
1343 break;
1345 update_commandline();
1346 return ret;
1349 void search_not_found(void)
1351 const char *what = "Track";
1353 if (search_restricted) {
1354 switch (cur_view) {
1355 case TREE_VIEW:
1356 what = "Artist/album";
1357 break;
1358 case SORTED_VIEW:
1359 case PLAYLIST_VIEW:
1360 case QUEUE_VIEW:
1361 what = "Title";
1362 break;
1363 case BROWSER_VIEW:
1364 what = "File/Directory";
1365 break;
1366 case FILTERS_VIEW:
1367 what = "Filter";
1368 break;
1369 case HELP_VIEW:
1370 what = "Binding/command/option";
1371 break;
1373 } else {
1374 switch (cur_view) {
1375 case TREE_VIEW:
1376 case SORTED_VIEW:
1377 case PLAYLIST_VIEW:
1378 case QUEUE_VIEW:
1379 what = "Track";
1380 break;
1381 case BROWSER_VIEW:
1382 what = "File/Directory";
1383 break;
1384 case FILTERS_VIEW:
1385 what = "Filter";
1386 break;
1387 case HELP_VIEW:
1388 what = "Binding/command/option";
1389 break;
1392 info_msg("%s not found: %s", what, search_str ? : "");
1395 void set_view(int view)
1397 if (view == cur_view)
1398 return;
1400 cur_view = view;
1401 switch (cur_view) {
1402 case TREE_VIEW:
1403 searchable = tree_searchable;
1404 break;
1405 case SORTED_VIEW:
1406 searchable = lib_editable.searchable;
1407 break;
1408 case PLAYLIST_VIEW:
1409 searchable = pl_editable.searchable;
1410 break;
1411 case QUEUE_VIEW:
1412 searchable = pq_editable.searchable;
1413 break;
1414 case BROWSER_VIEW:
1415 searchable = browser_searchable;
1416 break;
1417 case FILTERS_VIEW:
1418 searchable = filters_searchable;
1419 break;
1420 case HELP_VIEW:
1421 searchable = help_searchable;
1422 update_help_window();
1423 break;
1426 curs_set(0);
1427 do_update_view(1);
1428 post_update();
1431 void enter_command_mode(void)
1433 error_buf[0] = 0;
1434 error_time = 0;
1435 input_mode = COMMAND_MODE;
1436 update_commandline();
1439 void enter_search_mode(void)
1441 error_buf[0] = 0;
1442 error_time = 0;
1443 input_mode = SEARCH_MODE;
1444 search_direction = SEARCH_FORWARD;
1445 update_commandline();
1448 void enter_search_backward_mode(void)
1450 error_buf[0] = 0;
1451 error_time = 0;
1452 input_mode = SEARCH_MODE;
1453 search_direction = SEARCH_BACKWARD;
1454 update_commandline();
1457 void update_colors(void)
1459 int i;
1461 if (!ui_initialized)
1462 return;
1464 for (i = 0; i < NR_CURSED; i++) {
1465 int bg = colors[cursed_to_bg_idx[i]];
1466 int fg = colors[cursed_to_fg_idx[i]];
1467 int pair = i + 1;
1469 if (fg >= 8 && fg <= 15) {
1470 /* fg colors 8..15 are special (0..7 + bold) */
1471 init_pair(pair, fg & 7, bg);
1472 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1473 } else {
1474 init_pair(pair, fg, bg);
1475 pairs[i] = COLOR_PAIR(pair);
1480 static void clear_error(void)
1482 time_t t = time(NULL);
1484 /* prevent accidental clearing of error messages */
1485 if (t - error_time < 2)
1486 return;
1488 if (error_buf[0]) {
1489 error_time = 0;
1490 error_buf[0] = 0;
1491 update_commandline();
1495 /* screen updates }}} */
1497 static void spawn_status_program(void)
1499 static const char *status_strs[] = { "stopped", "playing", "paused" };
1500 const char *stream_title = NULL;
1501 char *argv[32];
1502 int i, status;
1504 if (status_display_program == NULL || status_display_program[0] == 0)
1505 return;
1507 player_info_lock();
1508 status = player_info.status;
1509 if (status == 1)
1510 stream_title = get_stream_title();
1512 i = 0;
1513 argv[i++] = xstrdup(status_display_program);
1515 argv[i++] = xstrdup("status");
1516 argv[i++] = xstrdup(status_strs[status]);
1517 if (player_info.ti) {
1518 static const char *keys[] = {
1519 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1521 int j;
1523 if (is_url(player_info.ti->filename)) {
1524 argv[i++] = xstrdup("url");
1525 argv[i++] = xstrdup(player_info.ti->filename);
1526 if (stream_title) {
1527 argv[i++] = xstrdup("title");
1528 argv[i++] = xstrdup(stream_title);
1530 } else {
1531 char buf[32];
1533 argv[i++] = xstrdup("file");
1534 argv[i++] = xstrdup(player_info.ti->filename);
1535 for (j = 0; keys[j]; j++) {
1536 const char *key = keys[j];
1537 const char *val;
1539 val = keyvals_get_val(player_info.ti->comments, key);
1540 if (val) {
1541 argv[i++] = xstrdup(key);
1542 argv[i++] = xstrdup(val);
1545 snprintf(buf, sizeof(buf), "%d", player_info.ti->duration);
1546 argv[i++] = xstrdup("duration");
1547 argv[i++] = xstrdup(buf);
1550 argv[i++] = NULL;
1551 player_info_unlock();
1553 if (spawn(argv, &status) == -1)
1554 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1555 for (i = 0; argv[i]; i++)
1556 free(argv[i]);
1559 static int ctrl_c_pressed = 0;
1561 static void sig_int(int sig)
1563 ctrl_c_pressed = 1;
1566 static void sig_hup(int sig)
1568 cmus_running = 0;
1571 static int needs_to_resize = 1;
1573 static void sig_winch(int sig)
1575 needs_to_resize = 1;
1578 static int get_window_size(int *lines, int *columns)
1580 struct winsize ws;
1582 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1583 return -1;
1584 *columns = ws.ws_col;
1585 *lines = ws.ws_row;
1586 return 0;
1589 static void resize_tree_view(int w, int h)
1591 tree_win_w = w / 3;
1592 track_win_w = w - tree_win_w - 1;
1593 if (tree_win_w < 8)
1594 tree_win_w = 8;
1595 if (track_win_w < 8)
1596 track_win_w = 8;
1597 tree_win_x = 0;
1598 tree_win_y = 0;
1599 track_win_x = tree_win_w + 1;
1600 track_win_y = 0;
1602 h--;
1603 window_set_nr_rows(lib_tree_win, h);
1604 window_set_nr_rows(lib_track_win, h);
1607 static void update(void)
1609 int needs_view_update = 0;
1610 int needs_title_update = 0;
1611 int needs_status_update = 0;
1612 int needs_command_update = 0;
1613 int needs_spawn = 0;
1615 if (needs_to_resize) {
1616 int w, h;
1617 int columns, lines;
1619 if (get_window_size(&lines, &columns) == 0) {
1620 needs_to_resize = 0;
1621 resizeterm(lines, columns);
1622 w = COLS;
1623 h = LINES - 3;
1624 if (w < 16)
1625 w = 16;
1626 if (h < 8)
1627 h = 8;
1628 editable_lock();
1629 resize_tree_view(w, h);
1630 window_set_nr_rows(lib_editable.win, h - 1);
1631 window_set_nr_rows(pl_editable.win, h - 1);
1632 window_set_nr_rows(pq_editable.win, h - 1);
1633 window_set_nr_rows(filters_win, h - 1);
1634 window_set_nr_rows(help_win, h - 1);
1635 window_set_nr_rows(browser_win, h - 1);
1636 editable_unlock();
1637 needs_title_update = 1;
1638 needs_status_update = 1;
1639 needs_command_update = 1;
1643 player_info_lock();
1644 editable_lock();
1646 needs_spawn = player_info.status_changed || player_info.file_changed ||
1647 player_info.metadata_changed;
1649 if (player_info.file_changed) {
1650 player_info.file_changed = 0;
1651 needs_title_update = 1;
1652 needs_status_update = 1;
1654 if (player_info.metadata_changed) {
1655 player_info.metadata_changed = 0;
1656 needs_title_update = 1;
1658 if (player_info.position_changed || player_info.status_changed) {
1659 player_info.position_changed = 0;
1660 player_info.status_changed = 0;
1662 needs_status_update = 1;
1664 switch (cur_view) {
1665 case TREE_VIEW:
1666 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
1667 break;
1668 case SORTED_VIEW:
1669 needs_view_update += lib_editable.win->changed;
1670 break;
1671 case PLAYLIST_VIEW:
1672 needs_view_update += pl_editable.win->changed;
1673 break;
1674 case QUEUE_VIEW:
1675 needs_view_update += pq_editable.win->changed;
1676 break;
1677 case BROWSER_VIEW:
1678 needs_view_update += browser_win->changed;
1679 break;
1680 case FILTERS_VIEW:
1681 needs_view_update += filters_win->changed;
1682 break;
1683 case HELP_VIEW:
1684 needs_view_update += help_win->changed;
1685 break;
1688 /* total time changed? */
1689 if (play_library) {
1690 needs_status_update += lib_editable.win->changed;
1691 lib_editable.win->changed = 0;
1692 } else {
1693 needs_status_update += pl_editable.win->changed;
1694 lib_editable.win->changed = 0;
1697 editable_unlock();
1698 player_info_unlock();
1700 if (needs_spawn)
1701 spawn_status_program();
1703 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1704 curs_set(0);
1706 if (needs_view_update)
1707 do_update_view(0);
1708 if (needs_title_update)
1709 do_update_titleline();
1710 if (needs_status_update)
1711 do_update_statusline();
1712 if (needs_command_update)
1713 do_update_commandline();
1714 post_update();
1718 static void handle_ch(uchar ch)
1720 clear_error();
1721 if (input_mode == NORMAL_MODE) {
1722 normal_mode_ch(ch);
1723 } else if (input_mode == COMMAND_MODE) {
1724 command_mode_ch(ch);
1725 update_commandline();
1726 } else if (input_mode == SEARCH_MODE) {
1727 search_mode_ch(ch);
1728 update_commandline();
1732 static void handle_key(int key)
1734 clear_error();
1735 if (input_mode == NORMAL_MODE) {
1736 normal_mode_key(key);
1737 } else if (input_mode == COMMAND_MODE) {
1738 command_mode_key(key);
1739 update_commandline();
1740 } else if (input_mode == SEARCH_MODE) {
1741 search_mode_key(key);
1742 update_commandline();
1746 static void u_getch(void)
1748 int key;
1749 int bit = 7;
1750 int mask = (1 << 7);
1751 uchar u, ch;
1753 key = getch();
1754 if (key == ERR || key == 0)
1755 return;
1757 if (key > 255) {
1758 handle_key(key);
1759 return;
1762 ch = (unsigned char)key;
1763 while (bit > 0 && ch & mask) {
1764 mask >>= 1;
1765 bit--;
1767 if (bit == 7) {
1768 /* ascii */
1769 u = ch;
1770 } else {
1771 int count;
1773 u = ch & ((1 << bit) - 1);
1774 count = 6 - bit;
1775 while (count) {
1776 key = getch();
1777 if (key == ERR || key == 0)
1778 return;
1780 ch = (unsigned char)key;
1781 u = (u << 6) | (ch & 63);
1782 count--;
1785 handle_ch(u);
1788 static void main_loop(void)
1790 int rc, fd_high;
1792 fd_high = server_socket;
1793 while (cmus_running) {
1794 fd_set set;
1795 struct timeval tv;
1796 int poll_mixer = 0;
1797 int i, nr_fds = 0;
1798 int fds[NR_MIXER_FDS];
1799 struct list_head *item;
1800 struct client *client;
1802 update();
1804 /* Timeout must be so small that screen updates seem instant.
1805 * Only affects changes done in other threads (worker, player).
1807 * Too small timeout makes window updates too fast (wastes CPU).
1809 * Too large timeout makes status line (position) updates too slow.
1810 * The timeout is accuracy of player position.
1812 tv.tv_sec = 0;
1813 tv.tv_usec = 0;
1815 player_info_lock();
1816 if (player_info.status == PLAYER_STATUS_PLAYING) {
1817 // player position updates need to be fast
1818 tv.tv_usec = 100e3;
1820 player_info_unlock();
1822 if (!tv.tv_usec && worker_has_job(JOB_TYPE_ANY)) {
1823 // playlist is loading. screen needs to be updated
1824 tv.tv_usec = 250e3;
1827 FD_ZERO(&set);
1828 FD_SET(0, &set);
1829 FD_SET(server_socket, &set);
1830 list_for_each_entry(client, &client_head, node) {
1831 FD_SET(client->fd, &set);
1832 if (client->fd > fd_high)
1833 fd_high = client->fd;
1835 if (!soft_vol) {
1836 nr_fds = mixer_get_fds(fds);
1837 if (nr_fds == -OP_ERROR_NOT_SUPPORTED) {
1838 // mixer has no pollable file descriptors
1839 poll_mixer = 1;
1840 if (!tv.tv_usec)
1841 tv.tv_usec = 500e3;
1843 for (i = 0; i < nr_fds; i++) {
1844 BUG_ON(fds[i] <= 0);
1845 FD_SET(fds[i], &set);
1846 if (fds[i] > fd_high)
1847 fd_high = fds[i];
1851 if (tv.tv_usec) {
1852 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1853 } else {
1854 rc = select(fd_high + 1, &set, NULL, NULL, NULL);
1856 if (poll_mixer) {
1857 int ol = volume_l;
1858 int or = volume_r;
1860 mixer_read_volume();
1861 if (ol != volume_l || or != volume_r)
1862 update_statusline();
1865 if (rc <= 0) {
1866 if (ctrl_c_pressed) {
1867 handle_ch(0x03);
1868 ctrl_c_pressed = 0;
1871 continue;
1874 for (i = 0; i < nr_fds; i++) {
1875 if (FD_ISSET(fds[i], &set)) {
1876 d_print("vol changed\n");
1877 mixer_read_volume();
1878 update_statusline();
1881 if (FD_ISSET(server_socket, &set))
1882 server_accept();
1884 // server_serve() can remove client from the list
1885 item = client_head.next;
1886 while (item != &client_head) {
1887 struct list_head *next = item->next;
1888 client = container_of(item, struct client, node);
1889 if (FD_ISSET(client->fd, &set))
1890 server_serve(client);
1891 item = next;
1894 if (FD_ISSET(0, &set)) {
1895 if (using_utf8) {
1896 u_getch();
1897 } else {
1898 int key = getch();
1900 if (key != ERR && key != 0) {
1901 if (key > 255) {
1902 handle_key(key);
1903 } else {
1904 uchar ch = key;
1906 if (ch > 0x7f)
1907 ch |= U_INVALID_MASK;
1908 handle_ch(ch);
1916 static int get_next(struct track_info **ti)
1918 struct track_info *info;
1920 editable_lock();
1921 info = play_queue_remove();
1922 if (info == NULL) {
1923 if (play_library) {
1924 info = lib_set_next();
1925 } else {
1926 info = pl_set_next();
1929 editable_unlock();
1931 if (info == NULL)
1932 return -1;
1934 *ti = info;
1935 return 0;
1938 static const struct player_callbacks player_callbacks = {
1939 .get_next = get_next
1942 static void init_curses(void)
1944 struct sigaction act;
1945 char *ptr, *term;
1947 sigemptyset(&act.sa_mask);
1948 act.sa_flags = 0;
1949 act.sa_handler = sig_int;
1950 sigaction(SIGINT, &act, NULL);
1952 sigemptyset(&act.sa_mask);
1953 act.sa_flags = 0;
1954 act.sa_handler = sig_hup;
1955 sigaction(SIGHUP, &act, NULL);
1957 sigemptyset(&act.sa_mask);
1958 act.sa_flags = 0;
1959 act.sa_handler = SIG_IGN;
1960 sigaction(SIGPIPE, &act, NULL);
1962 sigemptyset(&act.sa_mask);
1963 act.sa_flags = 0;
1964 act.sa_handler = sig_winch;
1965 sigaction(SIGWINCH, &act, NULL);
1967 initscr();
1969 /* turn off kb buffering */
1970 cbreak();
1972 keypad(stdscr, TRUE);
1974 /* wait max 5 * 0.1 s if there are no keys available
1975 * doesn't really matter because we use select()
1977 halfdelay(5);
1979 noecho();
1980 if (has_colors()) {
1981 start_color();
1982 use_default_colors();
1984 d_print("Number of supported colors: %d\n", COLORS);
1985 ui_initialized = 1;
1987 /* this was disabled while initializing because it needs to be
1988 * called only once after all colors have been set
1990 update_colors();
1992 ptr = print_buffer;
1993 t_ts = tgetstr("ts", &ptr);
1994 t_fs = tgetstr("fs", &ptr);
1995 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
1997 if (!t_fs)
1998 t_ts = NULL;
2000 if (!t_ts && (term = getenv("TERM"))) {
2002 * Eterm: Eterm
2003 * aterm: rxvt
2004 * mlterm: xterm
2005 * terminal (xfce): xterm
2006 * urxvt: rxvt-unicode
2007 * xterm: xterm, xterm-{,16,88,256}color
2009 if (!strcmp(term, "screen")) {
2010 t_ts = "\033_";
2011 t_fs = "\033\\";
2012 } else if (!strncmp(term, "xterm", 5) ||
2013 !strncmp(term, "rxvt", 4) ||
2014 !strcmp(term, "Eterm")) {
2015 /* \033]1; change icon
2016 * \033]2; change title
2017 * \033]0; change both
2019 t_ts = "\033]0;";
2020 t_fs = "\007";
2025 static void init_all(void)
2027 server_init(server_address);
2029 /* does not select output plugin */
2030 player_init(&player_callbacks);
2032 /* plugins have been loaded so we know what plugin options are available */
2033 options_add();
2035 lib_init();
2036 searchable = tree_searchable;
2037 pl_init();
2038 cmus_init();
2039 browser_init();
2040 filters_init();
2041 help_init();
2042 cmdline_init();
2043 commands_init();
2044 search_mode_init();
2046 /* almost everything must be initialized now */
2047 options_load();
2049 /* finally we can set the output plugin */
2050 player_set_op(output_plugin);
2051 if (!soft_vol)
2052 mixer_open();
2054 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
2055 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
2056 pl_filename = xstrdup(pl_autosave_filename);
2057 lib_filename = xstrdup(lib_autosave_filename);
2059 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
2060 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
2062 if (error_count) {
2063 char buf[16];
2065 warn("Press <enter> to continue.");
2066 fgets(buf, sizeof(buf), stdin);
2068 help_add_all_unbound();
2070 init_curses();
2073 static void exit_all(void)
2075 endwin();
2077 options_exit();
2079 server_exit();
2080 cmus_exit();
2081 cmus_save(lib_for_each, lib_autosave_filename);
2082 cmus_save(pl_for_each, pl_autosave_filename);
2084 player_exit();
2085 commands_exit();
2086 search_mode_exit();
2087 filters_exit();
2088 help_exit();
2089 browser_exit();
2092 enum {
2093 FLAG_LISTEN,
2094 FLAG_PLUGINS,
2095 FLAG_HELP,
2096 FLAG_VERSION,
2097 NR_FLAGS
2100 static struct option options[NR_FLAGS + 1] = {
2101 { 0, "listen", 1 },
2102 { 0, "plugins", 0 },
2103 { 0, "help", 0 },
2104 { 0, "version", 0 },
2105 { 0, NULL, 0 }
2108 static const char *usage =
2109 "Usage: %s [OPTION]...\n"
2110 "Curses based music player.\n"
2111 "\n"
2112 " --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
2113 " ADDR is either a UNIX socket or host[:port]\n"
2114 " WARNING: using TCP/IP is insecure!\n"
2115 " --plugins list available plugins and exit\n"
2116 " --help display this help and exit\n"
2117 " --version " VERSION "\n"
2118 "\n"
2119 "Use cmus-remote to control cmus from command line.\n"
2120 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2122 int main(int argc, char *argv[])
2124 int list_plugins = 0;
2126 program_name = argv[0];
2127 argv++;
2128 while (1) {
2129 int idx;
2130 char *arg;
2132 idx = get_option(&argv, options, &arg);
2133 if (idx < 0)
2134 break;
2136 switch (idx) {
2137 case FLAG_HELP:
2138 printf(usage, program_name);
2139 return 0;
2140 case FLAG_VERSION:
2141 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
2142 return 0;
2143 case FLAG_PLUGINS:
2144 list_plugins = 1;
2145 break;
2146 case FLAG_LISTEN:
2147 server_address = xstrdup(arg);
2148 break;
2152 setlocale(LC_CTYPE, "");
2153 #ifdef CODESET
2154 charset = nl_langinfo(CODESET);
2155 #else
2156 charset = "ISO-8859-1";
2157 #endif
2158 if (strcmp(charset, "UTF-8") == 0) {
2159 using_utf8 = 1;
2160 } else {
2161 using_utf8 = 0;
2163 misc_init();
2164 if (server_address == NULL)
2165 server_address = xstrjoin(cmus_config_dir, "/socket");
2166 debug_init();
2167 d_print("charset = '%s'\n", charset);
2169 player_load_plugins();
2170 if (list_plugins) {
2171 player_dump_plugins();
2172 return 0;
2174 init_all();
2175 main_loop();
2176 exit_all();
2177 return 0;