add :refresh command
[cmus.git] / ui_curses.c
blob0180516d0cc3cc2c64e55253353ff6b72c43227b
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include <ui_curses.h>
21 #include <cmdline.h>
22 #include <search_mode.h>
23 #include <command_mode.h>
24 #include <options.h>
25 #include <play_queue.h>
26 #include <browser.h>
27 #include <filters.h>
28 #include <cmus.h>
29 #include <player.h>
30 #include <utils.h>
31 #include <lib.h>
32 #include <pl.h>
33 #include <xmalloc.h>
34 #include <xstrjoin.h>
35 #include <window.h>
36 #include <format_print.h>
37 #include <misc.h>
38 #include <prog.h>
39 #include <uchar.h>
40 #include <spawn.h>
41 #include <server.h>
42 #include <keys.h>
43 #include <debug.h>
44 #include <config.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <errno.h>
50 #include <sys/ioctl.h>
51 #include <curses.h>
52 #include <ctype.h>
53 #include <dirent.h>
54 #include <locale.h>
55 #include <langinfo.h>
56 #include <iconv.h>
57 #include <signal.h>
58 #include <stdarg.h>
60 /* globals. documented in ui_curses.h */
62 int ui_initialized = 0;
63 enum ui_input_mode input_mode = NORMAL_MODE;
64 int cur_view = TREE_VIEW;
65 struct searchable *searchable;
67 /* display parse errors? (command line) */
68 int display_errors = 0;
70 char *lib_autosave_filename;
71 char *pl_autosave_filename;
72 char *lib_filename = NULL;
73 char *pl_filename = NULL;
75 /* ------------------------------------------------------------------------- */
77 /* currently playing file */
78 static struct track_info *cur_track_info;
80 static int update_window_title = 0;
82 static int running = 1;
84 /* shown error message and time stamp
85 * error is cleared if it is older than 3s and key was pressed
87 static char error_buf[512];
88 static time_t error_time = 0;
89 /* info messages are displayed in different color */
90 static int msg_is_error;
91 static int error_count = 0;
93 static char *server_address = NULL;
94 static int remote_socket = -1;
96 static char *charset = NULL;
97 static char print_buffer[512];
99 /* destination buffer for utf8_encode and utf8_decode */
100 static char conv_buffer[512];
102 #define print_buffer_size (sizeof(print_buffer) - 1)
103 static int using_utf8;
105 static int tree_win_x = 0;
106 static int tree_win_y = 0;
107 static int tree_win_w = 0;
109 static int track_win_x = 0;
110 static int track_win_y = 0;
111 static int track_win_w = 0;
113 enum {
114 CURSED_WIN,
115 CURSED_WIN_CUR,
116 CURSED_WIN_SEL,
117 CURSED_WIN_SEL_CUR,
119 CURSED_WIN_ACTIVE,
120 CURSED_WIN_ACTIVE_CUR,
121 CURSED_WIN_ACTIVE_SEL,
122 CURSED_WIN_ACTIVE_SEL_CUR,
124 CURSED_SEPARATOR,
125 CURSED_WIN_TITLE,
126 CURSED_COMMANDLINE,
127 CURSED_STATUSLINE,
129 CURSED_TITLELINE,
130 CURSED_DIR,
131 CURSED_ERROR,
132 CURSED_INFO,
134 NR_CURSED
137 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
138 COLOR_WIN_BG,
139 COLOR_WIN_BG,
140 COLOR_WIN_INACTIVE_SEL_BG,
141 COLOR_WIN_INACTIVE_CUR_SEL_BG,
143 COLOR_WIN_BG,
144 COLOR_WIN_BG,
145 COLOR_WIN_SEL_BG,
146 COLOR_WIN_CUR_SEL_BG,
148 COLOR_WIN_BG,
149 COLOR_WIN_TITLE_BG,
150 COLOR_CMDLINE_BG,
151 COLOR_STATUSLINE_BG,
153 COLOR_TITLELINE_BG,
154 COLOR_WIN_BG,
155 COLOR_CMDLINE_BG,
156 COLOR_CMDLINE_BG
159 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
160 COLOR_WIN_FG,
161 COLOR_WIN_CUR,
162 COLOR_WIN_INACTIVE_SEL_FG,
163 COLOR_WIN_INACTIVE_CUR_SEL_FG,
165 COLOR_WIN_FG,
166 COLOR_WIN_CUR,
167 COLOR_WIN_SEL_FG,
168 COLOR_WIN_CUR_SEL_FG,
170 COLOR_SEPARATOR,
171 COLOR_WIN_TITLE_FG,
172 COLOR_CMDLINE_FG,
173 COLOR_STATUSLINE_FG,
175 COLOR_TITLELINE_FG,
176 COLOR_WIN_DIR,
177 COLOR_ERROR,
178 COLOR_INFO
181 /* index is CURSED_*, value is fucking color pair */
182 static int pairs[NR_CURSED];
184 enum {
185 TF_ARTIST,
186 TF_ALBUM,
187 TF_DISC,
188 TF_TRACK,
189 TF_TITLE,
190 TF_YEAR,
191 TF_GENRE,
192 TF_DURATION,
193 TF_PATHFILE,
194 TF_FILE,
195 NR_TFS
198 static struct format_option track_fopts[NR_TFS + 1] = {
199 DEF_FO_STR('a'),
200 DEF_FO_STR('l'),
201 DEF_FO_INT('D'),
202 DEF_FO_INT('n'),
203 DEF_FO_STR('t'),
204 DEF_FO_STR('y'),
205 DEF_FO_STR('g'),
206 DEF_FO_TIME('d'),
207 DEF_FO_STR('f'),
208 DEF_FO_STR('F'),
209 DEF_FO_END
212 enum {
213 SF_STATUS,
214 SF_POSITION,
215 SF_DURATION,
216 SF_TOTAL,
217 SF_VOLUME,
218 SF_LVOLUME,
219 SF_RVOLUME,
220 SF_BUFFER,
221 SF_REPEAT,
222 SF_CONTINUE,
223 SF_SHUFFLE,
224 SF_PLAYLISTMODE,
225 NR_SFS
228 static struct format_option status_fopts[NR_SFS + 1] = {
229 DEF_FO_STR('s'),
230 DEF_FO_TIME('p'),
231 DEF_FO_TIME('d'),
232 DEF_FO_TIME('t'),
233 DEF_FO_INT('v'),
234 DEF_FO_INT('l'),
235 DEF_FO_INT('r'),
236 DEF_FO_INT('b'),
237 DEF_FO_STR('R'),
238 DEF_FO_STR('C'),
239 DEF_FO_STR('S'),
240 DEF_FO_STR('L'),
241 DEF_FO_END
244 static void utf8_encode(const char *buffer)
246 static iconv_t cd = (iconv_t)-1;
247 size_t is, os;
248 const char *i;
249 char *o;
250 int rc;
252 if (cd == (iconv_t)-1) {
253 d_print("iconv_open(UTF-8, %s)\n", charset);
254 cd = iconv_open("UTF-8", charset);
255 if (cd == (iconv_t)-1) {
256 d_print("iconv_open failed: %s\n", strerror(errno));
257 return;
260 i = buffer;
261 o = conv_buffer;
262 is = strlen(i);
263 os = sizeof(conv_buffer) - 1;
264 rc = iconv(cd, (void *)&i, &is, &o, &os);
265 *o = 0;
266 if (rc == -1) {
267 d_print("iconv failed: %s\n", strerror(errno));
268 return;
272 static void utf8_decode(const char *buffer)
274 static iconv_t cd = (iconv_t)-1;
275 size_t is, os;
276 const char *i;
277 char *o;
278 int rc;
280 if (cd == (iconv_t)-1) {
281 d_print("iconv_open(%s, UTF-8)\n", charset);
282 cd = iconv_open(charset, "UTF-8");
283 if (cd == (iconv_t)-1) {
284 d_print("iconv_open failed: %s\n", strerror(errno));
285 return;
288 i = buffer;
289 o = conv_buffer;
290 is = strlen(i);
291 os = sizeof(conv_buffer) - 1;
292 rc = iconv(cd, (void *)&i, &is, &o, &os);
293 *o = 0;
294 if (rc == -1) {
295 d_print("iconv failed: %s\n", strerror(errno));
296 return;
300 /* screen updates {{{ */
302 static void dump_print_buffer(int row, int col)
304 if (using_utf8) {
305 mvaddstr(row, col, print_buffer);
306 } else {
307 utf8_decode(print_buffer);
308 mvaddstr(row, col, conv_buffer);
312 static void sprint(int row, int col, const char *str, int width, int indent)
314 int s, d, ellipsis_pos = 0, cut_double_width = 0;
316 width -= 2 + indent;
317 d = indent + 1;
318 memset(print_buffer, ' ', d);
319 s = 0;
320 while (1) {
321 uchar u;
322 int w;
324 if (width == 3)
325 ellipsis_pos = d;
327 u_get_char(str, &s, &u);
328 if (u == 0) {
329 memset(print_buffer + d, ' ', width);
330 d += width;
331 break;
334 w = u_char_width(u);
335 if (width == 3)
336 ellipsis_pos = d;
337 if (width == 4 && w == 2) {
338 /* can't cut double-width char */
339 ellipsis_pos = d + 1;
340 cut_double_width = 1;
343 width -= w;
344 if (width < 0) {
345 /* does not fit */
346 d = ellipsis_pos;
347 if (cut_double_width) {
348 /* first half of the double-width char */
349 print_buffer[d - 1] = ' ';
351 print_buffer[d++] = '.';
352 print_buffer[d++] = '.';
353 print_buffer[d++] = '.';
354 break;
356 u_set_char(print_buffer, &d, u);
358 print_buffer[d++] = ' ';
359 print_buffer[d++] = 0;
360 dump_print_buffer(row, col);
363 static void sprint_ascii(int row, int col, const char *str, int len)
365 int l;
367 l = strlen(str);
368 len -= 2;
370 print_buffer[0] = ' ';
371 if (l > len) {
372 memcpy(print_buffer + 1, str, len - 3);
373 print_buffer[len - 2] = '.';
374 print_buffer[len - 1] = '.';
375 print_buffer[len - 0] = '.';
376 } else {
377 memcpy(print_buffer + 1, str, l);
378 memset(print_buffer + 1 + l, ' ', len - l);
380 print_buffer[len + 1] = ' ';
381 print_buffer[len + 2] = 0;
382 mvaddstr(row, col, print_buffer);
385 static void print_tree(struct window *win, int row, struct iter *iter)
387 const char *noname = "<no name>";
388 struct artist *artist;
389 struct album *album;
390 struct iter sel;
391 int current, selected, active;
393 artist = iter_to_artist(iter);
394 album = iter_to_album(iter);
395 if (album) {
396 current = lib.cur_album == album;
397 } else {
398 current = lib.cur_artist == artist;
400 window_get_sel(win, &sel);
401 selected = iters_equal(iter, &sel);
402 active = lib.cur_win == lib.tree_win;
403 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
404 if (album) {
405 sprint(tree_win_y + row + 1, tree_win_x, album->name ? : noname, tree_win_w, 2);
406 } else {
407 sprint(tree_win_y + row + 1, tree_win_x, artist->name ? : noname, tree_win_w, 0);
411 static inline void fopt_set_str(struct format_option *fopt, const char *str)
413 BUG_ON(fopt->type != FO_STR);
414 if (str) {
415 fopt->fo_str = str;
416 fopt->empty = 0;
417 } else {
418 fopt->empty = 1;
422 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
424 BUG_ON(fopt->type != FO_INT);
425 fopt->fo_int = value;
426 fopt->empty = empty;
429 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
431 BUG_ON(fopt->type != FO_TIME);
432 fopt->fo_time = value;
433 fopt->empty = empty;
436 static void fill_track_fopts(struct tree_track *track)
438 const char *filename;
439 const struct track_info *ti = tree_track_info(track);
440 int num, disc;
442 if (using_utf8) {
443 filename = ti->filename;
444 } else {
445 utf8_encode(ti->filename);
446 filename = conv_buffer;
448 disc = track->shuffle_track.simple_track.disc;
449 num = track->shuffle_track.simple_track.num;
451 fopt_set_str(&track_fopts[TF_ARTIST], track->album->artist->name);
452 fopt_set_str(&track_fopts[TF_ALBUM], track->album->name);
453 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
454 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
455 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(ti->comments, "title"));
456 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(ti->comments, "date"));
457 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(ti->comments, "genre"));
458 fopt_set_time(&track_fopts[TF_DURATION], ti->duration, ti->duration == -1);
459 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
460 if (is_url(ti->filename)) {
461 fopt_set_str(&track_fopts[TF_FILE], filename);
462 } else {
463 const char *f;
465 f = strrchr(filename, '/');
466 if (f) {
467 fopt_set_str(&track_fopts[TF_FILE], f + 1);
468 } else {
469 fopt_set_str(&track_fopts[TF_FILE], filename);
474 static void fill_track_fopts_track_info(struct track_info *info)
476 char *filename;
477 int num, disc;
479 if (using_utf8) {
480 filename = info->filename;
481 } else {
482 utf8_encode(info->filename);
483 filename = conv_buffer;
485 disc = comments_get_int(info->comments, "discnumber");
486 num = comments_get_int(info->comments, "tracknumber");
488 fopt_set_str(&track_fopts[TF_ARTIST], comments_get_val(info->comments, "artist"));
489 fopt_set_str(&track_fopts[TF_ALBUM], comments_get_val(info->comments, "album"));
490 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
491 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
492 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(info->comments, "title"));
493 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(info->comments, "date"));
494 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(info->comments, "genre"));
495 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
496 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
497 if (is_url(info->filename)) {
498 fopt_set_str(&track_fopts[TF_FILE], filename);
499 } else {
500 const char *f;
502 f = strrchr(filename, '/');
503 if (f) {
504 fopt_set_str(&track_fopts[TF_FILE], f + 1);
505 } else {
506 fopt_set_str(&track_fopts[TF_FILE], filename);
511 static void print_track(struct window *win, int row, struct iter *iter)
513 struct tree_track *track;
514 struct iter sel;
515 int current, selected, active;
517 track = iter_to_tree_track(iter);
518 current = lib.cur_track == track;
519 window_get_sel(win, &sel);
520 selected = iters_equal(iter, &sel);
521 active = lib.cur_win == lib.track_win;
522 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
524 fill_track_fopts(track);
526 if (track_info_has_tag(tree_track_info(track))) {
527 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
528 } else {
529 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
531 dump_print_buffer(track_win_y + row + 1, track_win_x);
534 static void print_sorted(struct window *win, int row, struct iter *iter)
536 struct tree_track *track;
537 struct iter sel;
538 int current, selected, active = 1;
540 track = iter_to_sorted_track(iter);
541 current = lib.cur_track == track;
542 window_get_sel(win, &sel);
543 selected = iters_equal(iter, &sel);
544 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
546 fill_track_fopts(track);
548 if (track_info_has_tag(tree_track_info(track))) {
549 format_print(print_buffer, COLS, list_win_format, track_fopts);
550 } else {
551 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
553 dump_print_buffer(row + 1, 0);
556 static void print_pl(struct window *win, int row, struct iter *iter)
558 struct simple_track *track;
559 struct iter sel;
560 int current, selected, active;
562 track = iter_to_simple_track(iter);
563 current = pl_cur_track == track;
564 window_get_sel(win, &sel);
565 selected = iters_equal(iter, &sel);
567 active = 1;
568 if (!selected && track->marked) {
569 selected = 1;
570 active = 0;
573 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
575 fill_track_fopts_track_info(track->info);
577 if (track_info_has_tag(track->info)) {
578 format_print(print_buffer, COLS, list_win_format, track_fopts);
579 } else {
580 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
582 dump_print_buffer(row + 1, 0);
585 static void print_play_queue(struct window *win, int row, struct iter *iter)
587 struct simple_track *track;
588 struct iter sel;
589 int current, selected, active;
591 track = iter_to_simple_track(iter);
592 current = 0;
593 window_get_sel(win, &sel);
594 selected = iters_equal(iter, &sel);
596 active = 1;
597 if (!selected && track->marked) {
598 selected = 1;
599 active = 0;
602 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
604 fill_track_fopts_track_info(track->info);
606 if (track_info_has_tag(track->info)) {
607 format_print(print_buffer, COLS, list_win_format, track_fopts);
608 } else {
609 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
611 dump_print_buffer(row + 1, 0);
614 static void print_browser(struct window *win, int row, struct iter *iter)
616 struct browser_entry *e;
617 struct iter sel;
618 int selected;
620 e = iter_to_browser_entry(iter);
621 window_get_sel(win, &sel);
622 selected = iters_equal(iter, &sel);
623 if (selected) {
624 int active = 1;
625 int current = 0;
627 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
628 } else {
629 if (e->type == BROWSER_ENTRY_DIR) {
630 bkgdset(pairs[CURSED_DIR]);
631 } else {
632 bkgdset(pairs[CURSED_WIN]);
636 /* file name encoding == terminal encoding. no need to convert */
637 if (using_utf8) {
638 sprint(row + 1, 0, e->name, COLS, 0);
639 } else {
640 sprint_ascii(row + 1, 0, e->name, COLS);
644 static void print_filter(struct window *win, int row, struct iter *iter)
646 char buf[256];
647 struct filter_entry *e = iter_to_filter_entry(iter);
648 struct iter sel;
649 /* window active? */
650 int active = 1;
651 /* row selected? */
652 int selected;
653 /* is the filter currently active? */
654 int current = e->active;
656 window_get_sel(win, &sel);
657 selected = iters_equal(iter, &sel);
658 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
660 snprintf(buf, sizeof(buf), "%c %-15s %s", e->selected ? '*' : ' ', e->name, e->filter);
661 sprint(row + 1, 0, buf, COLS, 0);
664 static void update_window(struct window *win, int x, int y, int w, const char *title,
665 void (*print)(struct window *, int, struct iter *))
667 struct iter iter;
668 int nr_rows;
669 int c, i;
671 win->changed = 0;
673 bkgdset(pairs[CURSED_WIN_TITLE]);
674 c = snprintf(print_buffer, w + 1, " %s", title);
675 if (c > w)
676 c = w;
677 memset(print_buffer + c, ' ', w - c + 1);
678 print_buffer[w] = 0;
679 dump_print_buffer(y, x);
680 nr_rows = window_get_nr_rows(win);
681 i = 0;
682 if (window_get_top(win, &iter)) {
683 while (i < nr_rows) {
684 print(win, i, &iter);
685 i++;
686 if (!window_get_next(win, &iter))
687 break;
691 bkgdset(pairs[0]);
692 memset(print_buffer, ' ', w);
693 print_buffer[w] = 0;
694 while (i < nr_rows) {
695 dump_print_buffer(y + i + 1, x);
696 i++;
700 static void update_tree_window(void)
702 update_window(lib.tree_win, tree_win_x, tree_win_y,
703 tree_win_w, "Artist / Album", print_tree);
706 static void update_track_window(void)
708 char title[512];
710 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
711 update_window(lib.track_win, track_win_x, track_win_y,
712 track_win_w, title, print_track);
715 static const char *pretty(const char *path)
717 static int home_len = -1;
718 static char buf[256];
720 if (home_len == -1)
721 home_len = strlen(home_dir);
723 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
724 return path;
726 buf[0] = '~';
727 strcpy(buf + 1, path + home_len);
728 return buf;
731 static const char * const sorted_names[2] = { "", "sorted by " };
733 static void update_sorted_window(void)
735 char title[512];
736 char *filename;
738 filename = lib_filename ? lib_filename : lib_autosave_filename;
739 if (using_utf8) {
740 /* already UTF-8 */
741 } else {
742 utf8_encode(filename);
743 filename = conv_buffer;
745 snprintf(title, sizeof(title), "Library %s - %d tracks %s%s", pretty(filename),
746 lib.nr_tracks, sorted_names[lib_sort_str[0] != 0], lib_sort_str);
747 update_window(lib.sorted_win, 0, 0, COLS, title, print_sorted);
750 static void update_pl_window(void)
752 char title[512];
753 char *filename;
754 int pos;
756 filename = pl_filename ? pl_filename : pl_autosave_filename;
757 if (using_utf8) {
758 /* already UTF-8 */
759 } else {
760 utf8_encode(filename);
761 filename = conv_buffer;
764 snprintf(title, sizeof(title), "Playlist %s - %d tracks", pretty(filename), pl_nr_tracks);
765 if (pl_nr_marked) {
766 pos = strlen(title);
767 snprintf(title + pos, sizeof(title) - pos, " (%d marked)", pl_nr_marked);
769 pos = strlen(title);
770 snprintf(title + pos, sizeof(title) - pos, " %s%s",
771 sorted_names[pl_sort_str[0] != 0], pl_sort_str);
773 update_window(pl_win, 0, 0, COLS, title, print_pl);
776 static void update_play_queue_window(void)
778 char title[128];
780 snprintf(title, sizeof(title), "Play Queue - %d tracks", pq_nr_tracks);
781 if (pq_nr_marked) {
782 int pos = strlen(title);
784 snprintf(title + pos, sizeof(title) - pos, " (%d marked)", pq_nr_marked);
786 update_window(play_queue_win, 0, 0, COLS, title, print_play_queue);
789 static void update_browser_window(void)
791 char title[512];
792 char *dirname;
794 if (using_utf8) {
795 /* already UTF-8 */
796 dirname = browser_dir;
797 } else {
798 utf8_encode(browser_dir);
799 dirname = conv_buffer;
801 snprintf(title, sizeof(title), "Browser - %s", dirname);
802 update_window(browser_win, 0, 0, COLS, title, print_browser);
805 static void update_filters_window(void)
807 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
810 static void draw_separator(void)
812 int row;
814 bkgdset(pairs[CURSED_WIN_TITLE]);
815 mvaddch(0, tree_win_w, ' ');
816 bkgdset(pairs[CURSED_SEPARATOR]);
817 for (row = 1; row < LINES - 3; row++)
818 mvaddch(row, tree_win_w, ACS_VLINE);
821 static void do_update_view(int full)
823 switch (cur_view) {
824 case TREE_VIEW:
825 lib_lock();
826 if (full || lib.tree_win->changed)
827 update_tree_window();
828 if (full || lib.track_win->changed)
829 update_track_window();
830 lib_unlock();
831 draw_separator();
832 break;
833 case SORTED_VIEW:
834 lib_lock();
835 update_sorted_window();
836 lib_unlock();
837 break;
838 case PLAYLIST_VIEW:
839 pl_lock();
840 update_pl_window();
841 pl_unlock();
842 break;
843 case QUEUE_VIEW:
844 play_queue_lock();
845 update_play_queue_window();
846 play_queue_unlock();
847 break;
848 case BROWSER_VIEW:
849 update_browser_window();
850 break;
851 case FILTERS_VIEW:
852 update_filters_window();
853 break;
857 static void do_update_statusline(void)
859 static const char *status_strs[] = { ".", ">", "|" };
860 static const char *cont_strs[] = { " ", "C" };
861 static const char *repeat_strs[] = { " ", "R" };
862 static const char *shuffle_strs[] = { " ", "S" };
863 int play_sorted, buffer_fill, vol, vol_left, vol_right;
864 int duration = -1;
865 char *msg;
866 char format[80];
868 lib_lock();
869 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib.total_time : pl_total_time, 0);
870 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
871 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
872 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[lib.aaa_mode]);
873 play_sorted = lib.play_sorted;
874 lib_unlock();
876 if (cur_track_info)
877 duration = cur_track_info->duration;
879 player_info_lock();
881 vol_left = scale_to_percentage(player_info.vol_left, player_info.vol_max);
882 vol_right = scale_to_percentage(player_info.vol_right, player_info.vol_max);
883 vol = (vol_left + vol_right + 1) / 2;
884 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
886 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
888 if (show_remaining_time && duration != -1) {
889 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
890 } else {
891 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
894 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
895 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
896 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
897 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
898 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
899 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
901 strcpy(format, " %s %p ");
902 if (duration != -1)
903 strcat(format, "/ %d ");
904 if (player_info.vol_left != player_info.vol_right) {
905 strcat(format, "- %t vol: %l,%r ");
906 } else {
907 strcat(format, "- %t vol: %v ");
909 if (cur_track_info && is_url(cur_track_info->filename))
910 strcat(format, "buf: %b ");
911 strcat(format, "%=");
912 if (play_library) {
913 /* artist/album modes work only in lib */
914 if (shuffle) {
915 /* shuffle overrides sorted mode */
916 strcat(format, "%L from library");
917 } else if (play_sorted) {
918 strcat(format, "%L from sorted library");
919 } else {
920 strcat(format, "%L from library");
922 } else {
923 strcat(format, "playlist");
925 strcat(format, " | %1C%1R%1S ");
926 format_print(print_buffer, COLS, format, status_fopts);
928 msg = player_info.error_msg;
929 player_info.error_msg = NULL;
931 player_info_unlock();
933 bkgdset(pairs[CURSED_STATUSLINE]);
934 dump_print_buffer(LINES - 2, 0);
936 if (msg) {
937 error_msg("%s", msg);
938 free(msg);
942 static void dump_buffer(const char *buffer)
944 if (using_utf8) {
945 addstr(buffer);
946 } else {
947 utf8_decode(buffer);
948 addstr(conv_buffer);
952 static void do_update_commandline(void)
954 int w, idx;
955 char ch;
957 move(LINES - 1, 0);
958 if (error_buf[0]) {
959 if (msg_is_error) {
960 bkgdset(pairs[CURSED_ERROR]);
961 } else {
962 bkgdset(pairs[CURSED_INFO]);
964 addstr(error_buf);
965 clrtoeol();
966 return;
968 bkgdset(pairs[CURSED_COMMANDLINE]);
969 if (input_mode == NORMAL_MODE) {
970 clrtoeol();
971 return;
974 /* COMMAND_MODE or SEARCH_MODE */
975 w = u_str_width(cmdline.line);
976 ch = ':';
977 if (input_mode == SEARCH_MODE)
978 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
980 if (w <= COLS - 2) {
981 addch(ch);
982 idx = u_copy_chars(print_buffer, cmdline.line, &w);
983 print_buffer[idx] = 0;
984 dump_buffer(print_buffer);
985 clrtoeol();
986 } else {
987 /* keep cursor as far right as possible */
988 int skip, width, cw;
990 /* cursor pos (width, not chars. doesn't count the ':') */
991 cw = u_str_nwidth(cmdline.line, cmdline.cpos);
993 skip = cw + 2 - COLS;
994 if (skip > 0) {
995 /* skip the ':' */
996 skip--;
998 /* skip rest (if any) */
999 idx = u_skip_chars(cmdline.line, &skip);
1001 width = COLS;
1002 idx = u_copy_chars(print_buffer, cmdline.line + idx, &width);
1003 while (width < COLS) {
1004 /* cursor is at end of the buffer
1005 * print a space (or 2 if the last skipped character
1006 * was double-width)
1008 print_buffer[idx++] = ' ';
1009 width++;
1011 print_buffer[idx] = 0;
1012 dump_buffer(print_buffer);
1013 } else {
1014 /* print ':' + COLS - 1 chars */
1015 addch(ch);
1016 width = COLS - 1;
1017 idx = u_copy_chars(print_buffer, cmdline.line, &width);
1018 print_buffer[idx] = 0;
1019 dump_buffer(print_buffer);
1024 /* lock player_info! */
1025 static const char *get_stream_title(const char *metadata)
1027 static char stream_title[255 * 16 + 1];
1028 char *ptr, *title;
1030 ptr = strstr(player_info.metadata, "StreamTitle='");
1031 if (ptr == NULL)
1032 return NULL;
1033 ptr += 13;
1034 title = ptr;
1035 while (*ptr) {
1036 if (*ptr == '\'' && *(ptr + 1) == ';') {
1037 memcpy(stream_title, title, ptr - title);
1038 stream_title[ptr - title] = 0;
1039 return stream_title;
1041 ptr++;
1043 return NULL;
1046 static void do_update_titleline(void)
1048 bkgdset(pairs[CURSED_TITLELINE]);
1049 player_info_lock();
1050 if (cur_track_info) {
1051 const char *filename;
1052 int use_alt_format = 0;
1053 struct keyval *cur_comments = cur_track_info->comments;
1055 if (cur_comments[0].key == NULL) {
1056 const char *title = get_stream_title(player_info.metadata);
1058 if (title == NULL)
1059 use_alt_format = 1;
1060 fopt_set_str(&track_fopts[TF_ARTIST], NULL);
1061 fopt_set_str(&track_fopts[TF_ALBUM], NULL);
1062 fopt_set_int(&track_fopts[TF_DISC], -1, 1);
1063 fopt_set_int(&track_fopts[TF_TRACK], -1, 1);
1064 fopt_set_str(&track_fopts[TF_TITLE], title);
1065 fopt_set_str(&track_fopts[TF_YEAR], NULL);
1066 } else {
1067 int disc_num, track_num;
1069 disc_num = comments_get_int(cur_comments, "discnumber");
1070 track_num = comments_get_int(cur_comments, "tracknumber");
1071 fopt_set_str(&track_fopts[TF_ARTIST], comments_get_val(cur_comments, "artist"));
1072 fopt_set_str(&track_fopts[TF_ALBUM], comments_get_val(cur_comments, "album"));
1073 fopt_set_int(&track_fopts[TF_DISC], disc_num, disc_num == -1);
1074 fopt_set_int(&track_fopts[TF_TRACK], track_num, track_num == -1);
1075 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(cur_comments, "title"));
1076 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(cur_comments, "date"));
1078 fopt_set_time(&track_fopts[TF_DURATION],
1079 cur_track_info->duration,
1080 cur_track_info->duration == -1);
1081 fopt_set_str(&track_fopts[TF_PATHFILE], cur_track_info->filename);
1082 if (is_url(cur_track_info->filename)) {
1083 fopt_set_str(&track_fopts[TF_FILE], cur_track_info->filename);
1084 } else {
1085 filename = strrchr(cur_track_info->filename, '/');
1086 if (filename) {
1087 fopt_set_str(&track_fopts[TF_FILE], filename + 1);
1088 } else {
1089 fopt_set_str(&track_fopts[TF_FILE], cur_track_info->filename);
1092 if (use_alt_format) {
1093 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1094 } else {
1095 format_print(print_buffer, COLS, current_format, track_fopts);
1097 dump_print_buffer(LINES - 3, 0);
1099 if (update_window_title) {
1100 char *wtitle;
1101 int i;
1103 if (use_alt_format) {
1104 format_print(print_buffer, sizeof(print_buffer) - 1,
1105 window_title_alt_format, track_fopts);
1106 } else {
1107 format_print(print_buffer, sizeof(print_buffer) - 1,
1108 window_title_format, track_fopts);
1111 /* remove whitespace */
1112 i = sizeof(print_buffer) - 2;
1113 while (i > 0 && print_buffer[i] == ' ')
1114 i--;
1115 print_buffer[i + 1] = 0;
1117 if (using_utf8) {
1118 wtitle = print_buffer;
1119 } else {
1120 utf8_decode(print_buffer);
1121 wtitle = conv_buffer;
1124 printf("\033]0;%s\007", wtitle);
1125 fflush(stdout);
1127 } else {
1128 move(LINES - 3, 0);
1129 clrtoeol();
1131 if (update_window_title) {
1132 printf("\033]0;CMus " VERSION "\007");
1133 fflush(stdout);
1136 player_info_unlock();
1139 static int cmdline_cursor_column(void)
1141 int cw, skip, s;
1143 /* width of the text in the buffer before cursor */
1144 cw = u_str_nwidth(cmdline.line, cmdline.cpos);
1146 if (1 + cw < COLS) {
1147 /* whole line is visible */
1148 return 1 + cw;
1151 /* beginning of cmdline is not visible */
1153 /* check if the first visible char in cmdline would be halved
1154 * double-width character (or invalid byte <xx>) which is not possible.
1155 * we need to skip the whole character and move cursor to COLS - 2
1156 * column. */
1157 skip = cw + 2 - COLS;
1159 /* skip the ':' */
1160 skip--;
1162 /* skip rest */
1163 s = skip;
1164 u_skip_chars(cmdline.line, &s);
1165 if (s > skip) {
1166 /* the last skipped char was double-width or <xx> */
1167 return COLS - 1 - (s - skip);
1169 return COLS - 1;
1172 static void post_update(void)
1174 /* refresh makes cursor visible at least for urxvt */
1175 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1176 move(LINES - 1, cmdline_cursor_column());
1177 refresh();
1178 curs_set(1);
1179 } else {
1180 move(LINES - 1, 0);
1181 refresh();
1182 curs_set(0);
1186 void update_titleline(void)
1188 curs_set(0);
1189 do_update_titleline();
1190 post_update();
1193 void update_full(void)
1195 if (!ui_initialized)
1196 return;
1198 curs_set(0);
1200 do_update_view(1);
1201 do_update_titleline();
1202 do_update_statusline();
1203 do_update_commandline();
1205 post_update();
1208 static void update_commandline(void)
1210 curs_set(0);
1211 do_update_commandline();
1212 post_update();
1215 void update_statusline(void)
1217 if (!ui_initialized)
1218 return;
1220 curs_set(0);
1221 do_update_statusline();
1222 post_update();
1225 void info_msg(const char *format, ...)
1227 va_list ap;
1229 va_start(ap, format);
1230 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1231 va_end(ap);
1233 msg_is_error = 0;
1235 update_commandline();
1238 void error_msg(const char *format, ...)
1240 va_list ap;
1242 if (!display_errors)
1243 return;
1245 strcpy(error_buf, "Error: ");
1246 va_start(ap, format);
1247 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1248 va_end(ap);
1250 msg_is_error = 1;
1251 error_count++;
1253 if (ui_initialized) {
1254 error_time = time(NULL);
1255 update_commandline();
1256 } else {
1257 warn("%s\n", error_buf);
1258 error_buf[0] = 0;
1262 int yes_no_query(const char *format, ...)
1264 char buffer[512];
1265 va_list ap;
1266 int ret = 0;
1268 va_start(ap, format);
1269 vsnprintf(buffer, sizeof(buffer), format, ap);
1270 va_end(ap);
1272 move(LINES - 1, 0);
1273 bkgdset(pairs[CURSED_INFO]);
1275 /* no need to convert buffer.
1276 * it is always encoded in the right charset (assuming filenames are
1277 * encoded in same charset as LC_CTYPE).
1280 addstr(buffer);
1281 clrtoeol();
1282 refresh();
1284 while (1) {
1285 int ch = getch();
1287 if (ch == ERR || ch == 0)
1288 continue;
1289 if (ch == 'y')
1290 ret = 1;
1291 break;
1293 update_commandline();
1294 return ret;
1297 void search_not_found(void)
1299 const char *what = "Track";
1301 if (search_restricted) {
1302 switch (cur_view) {
1303 case TREE_VIEW:
1304 what = "Artist/album";
1305 break;
1306 case SORTED_VIEW:
1307 case PLAYLIST_VIEW:
1308 case QUEUE_VIEW:
1309 what = "Title";
1310 break;
1311 case BROWSER_VIEW:
1312 what = "File/Directory";
1313 break;
1314 case FILTERS_VIEW:
1315 what = "Filter";
1316 break;
1318 } else {
1319 switch (cur_view) {
1320 case TREE_VIEW:
1321 case SORTED_VIEW:
1322 case PLAYLIST_VIEW:
1323 case QUEUE_VIEW:
1324 what = "Track";
1325 break;
1326 case BROWSER_VIEW:
1327 what = "File/Directory";
1328 break;
1329 case FILTERS_VIEW:
1330 what = "Filter";
1331 break;
1334 info_msg("%s not found: %s", what, search_str ? : "");
1337 void set_view(int view)
1339 if (view == cur_view)
1340 return;
1341 cur_view = view;
1343 lib_lock();
1344 if (view < 2)
1345 __lib_set_view(view);
1346 switch (cur_view) {
1347 case TREE_VIEW:
1348 searchable = tree_searchable;
1349 update_tree_window();
1350 update_track_window();
1351 draw_separator();
1352 break;
1353 case SORTED_VIEW:
1354 searchable = sorted_searchable;
1355 update_sorted_window();
1356 break;
1357 case PLAYLIST_VIEW:
1358 searchable = pl_searchable;
1359 update_pl_window();
1360 break;
1361 case QUEUE_VIEW:
1362 searchable = play_queue_searchable;
1363 update_play_queue_window();
1364 break;
1365 case BROWSER_VIEW:
1366 searchable = browser_searchable;
1367 update_browser_window();
1368 break;
1369 case FILTERS_VIEW:
1370 searchable = filters_searchable;
1371 update_filters_window();
1372 break;
1374 lib_unlock();
1376 refresh();
1379 void enter_command_mode(void)
1381 error_buf[0] = 0;
1382 error_time = 0;
1383 input_mode = COMMAND_MODE;
1384 update_commandline();
1387 void enter_search_mode(void)
1389 error_buf[0] = 0;
1390 error_time = 0;
1391 input_mode = SEARCH_MODE;
1392 search_direction = SEARCH_FORWARD;
1393 update_commandline();
1396 void enter_search_backward_mode(void)
1398 error_buf[0] = 0;
1399 error_time = 0;
1400 input_mode = SEARCH_MODE;
1401 search_direction = SEARCH_BACKWARD;
1402 update_commandline();
1405 void quit(void)
1407 running = 0;
1410 void update_colors(void)
1412 int i;
1414 if (!ui_initialized)
1415 return;
1417 for (i = 0; i < NR_CURSED; i++) {
1418 int bg = colors[cursed_to_bg_idx[i]];
1419 int fg = colors[cursed_to_fg_idx[i]];
1420 int pair = i + 1;
1422 if (fg >= 8 && fg <= 15) {
1423 /* fg colors 8..15 are special (0..7 + bold) */
1424 init_pair(pair, fg & 7, bg);
1425 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1426 } else {
1427 init_pair(pair, fg, bg);
1428 pairs[i] = COLOR_PAIR(pair);
1431 curs_set(0);
1433 do_update_view(1);
1434 do_update_titleline();
1435 do_update_statusline();
1436 do_update_commandline();
1438 post_update();
1441 static void clear_error(void)
1443 time_t t = time(NULL);
1445 /* error msg is visible at least 3s */
1446 if (t - error_time < 3)
1447 return;
1449 if (error_buf[0]) {
1450 error_time = 0;
1451 error_buf[0] = 0;
1452 update_commandline();
1456 /* screen updates }}} */
1458 static void spawn_status_program(void)
1460 static const char *status_strs[] = { "stopped", "playing", "paused" };
1461 const char *stream_title = NULL;
1462 char *argv[32];
1463 int i, status;
1465 if (status_display_program == NULL || status_display_program[0] == 0)
1466 return;
1468 player_info_lock();
1469 status = player_info.status;
1470 if (status == 1)
1471 stream_title = get_stream_title(player_info.metadata);
1472 player_info_unlock();
1474 i = 0;
1475 argv[i++] = xstrdup(status_display_program);
1477 argv[i++] = xstrdup("status");
1478 argv[i++] = xstrdup(status_strs[status]);
1479 if (cur_track_info) {
1480 static const char *keys[] = {
1481 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1483 int j;
1485 if (is_url(cur_track_info->filename)) {
1486 argv[i++] = xstrdup("url");
1487 argv[i++] = xstrdup(cur_track_info->filename);
1488 if (stream_title) {
1489 argv[i++] = xstrdup("title");
1490 argv[i++] = xstrdup(stream_title);
1492 } else {
1493 char buf[32];
1495 argv[i++] = xstrdup("file");
1496 argv[i++] = xstrdup(cur_track_info->filename);
1497 for (j = 0; keys[j]; j++) {
1498 const char *key = keys[j];
1499 const char *val;
1501 val = comments_get_val(cur_track_info->comments, key);
1502 if (val) {
1503 argv[i++] = xstrdup(key);
1504 argv[i++] = xstrdup(val);
1507 snprintf(buf, sizeof(buf), "%d", cur_track_info->duration);
1508 argv[i++] = xstrdup("duration");
1509 argv[i++] = xstrdup(buf);
1512 argv[i++] = NULL;
1513 if (spawn(argv, &status) == -1)
1514 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1515 for (i = 0; argv[i]; i++)
1516 free(argv[i]);
1519 static int ctrl_c_pressed = 0;
1521 static void sig_int(int sig)
1523 ctrl_c_pressed = 1;
1526 static int needs_to_resize = 1;
1528 static void sig_winch(int sig)
1530 needs_to_resize = 1;
1533 static int get_window_size(int *lines, int *columns)
1535 struct winsize ws;
1537 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1538 return -1;
1539 *columns = ws.ws_col;
1540 *lines = ws.ws_row;
1541 return 0;
1544 static void resize_playlist(int w, int h)
1546 tree_win_w = w / 3;
1547 track_win_w = w - tree_win_w - 1;
1548 if (tree_win_w < 8)
1549 tree_win_w = 8;
1550 if (track_win_w < 8)
1551 track_win_w = 8;
1552 tree_win_x = 0;
1553 tree_win_y = 0;
1554 track_win_x = tree_win_w + 1;
1555 track_win_y = 0;
1557 h--;
1558 lib_lock();
1559 window_set_nr_rows(lib.tree_win, h);
1560 window_set_nr_rows(lib.track_win, h);
1561 window_set_nr_rows(lib.sorted_win, h);
1562 lib_unlock();
1565 static void update(void)
1567 int needs_view_update = 0;
1568 int needs_title_update = 0;
1569 int needs_status_update = 0;
1570 int needs_command_update = 0;
1571 int needs_spawn = 0;
1573 if (needs_to_resize) {
1574 int w, h;
1575 int columns, lines;
1577 if (get_window_size(&lines, &columns) == 0) {
1578 needs_to_resize = 0;
1579 resizeterm(lines, columns);
1580 w = COLS;
1581 h = LINES - 3;
1582 if (w < 16)
1583 w = 16;
1584 if (h < 8)
1585 h = 8;
1586 resize_playlist(w, h);
1587 window_set_nr_rows(filters_win, h - 1);
1588 window_set_nr_rows(browser_win, h - 1);
1589 window_set_nr_rows(play_queue_win, h - 1);
1590 window_set_nr_rows(pl_win, h - 1);
1591 needs_title_update = 1;
1592 needs_status_update = 1;
1593 needs_command_update = 1;
1597 player_info_lock();
1598 lib_lock();
1600 needs_spawn = player_info.status_changed || player_info.file_changed ||
1601 player_info.metadata_changed;
1603 if (player_info.file_changed) {
1604 if (cur_track_info)
1605 track_info_unref(cur_track_info);
1606 if (player_info.filename[0] == 0) {
1607 cur_track_info = NULL;
1608 } else {
1609 cur_track_info = cmus_get_track_info(player_info.filename);
1611 player_info.file_changed = 0;
1612 needs_title_update = 1;
1613 needs_status_update = 1;
1615 if (player_info.metadata_changed) {
1616 player_info.metadata_changed = 0;
1617 needs_title_update = 1;
1619 if (player_info.position_changed || player_info.status_changed || player_info.vol_changed) {
1620 player_info.position_changed = 0;
1621 player_info.status_changed = 0;
1622 player_info.vol_changed = 0;
1624 needs_status_update = 1;
1626 switch (cur_view) {
1627 case TREE_VIEW:
1628 needs_view_update += lib.tree_win->changed || lib.track_win->changed;
1629 break;
1630 case SORTED_VIEW:
1631 needs_view_update += lib.sorted_win->changed;
1632 break;
1633 case PLAYLIST_VIEW:
1634 needs_view_update += pl_win->changed;
1635 break;
1636 case QUEUE_VIEW:
1637 needs_view_update += play_queue_win->changed;
1638 break;
1639 case BROWSER_VIEW:
1640 needs_view_update += browser_win->changed;
1641 break;
1642 case FILTERS_VIEW:
1643 needs_view_update += filters_win->changed;
1644 break;
1647 /* total time changed? */
1648 if (play_library) {
1649 needs_status_update += lib.sorted_win->changed;
1650 } else {
1651 needs_status_update += pl_win->changed;
1654 lib_unlock();
1655 player_info_unlock();
1657 if (needs_spawn)
1658 spawn_status_program();
1660 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1661 curs_set(0);
1663 if (needs_view_update)
1664 do_update_view(0);
1665 if (needs_title_update)
1666 do_update_titleline();
1667 if (needs_status_update)
1668 do_update_statusline();
1669 if (needs_command_update)
1670 do_update_commandline();
1671 post_update();
1675 static void handle_ch(uchar ch)
1677 clear_error();
1678 if (input_mode == NORMAL_MODE) {
1679 normal_mode_ch(ch);
1680 } else if (input_mode == COMMAND_MODE) {
1681 command_mode_ch(ch);
1682 update_commandline();
1683 } else if (input_mode == SEARCH_MODE) {
1684 search_mode_ch(ch);
1685 update_commandline();
1689 static void handle_key(int key)
1691 clear_error();
1692 if (input_mode == NORMAL_MODE) {
1693 normal_mode_key(key);
1694 } else if (input_mode == COMMAND_MODE) {
1695 command_mode_key(key);
1696 update_commandline();
1697 } else if (input_mode == SEARCH_MODE) {
1698 search_mode_key(key);
1699 update_commandline();
1703 static void u_getch(void)
1705 int key;
1706 int bit = 7;
1707 int mask = (1 << 7);
1708 uchar u, ch;
1710 key = getch();
1711 if (key == ERR || key == 0)
1712 return;
1714 if (key > 255) {
1715 handle_key(key);
1716 return;
1719 ch = (unsigned char)key;
1720 while (bit > 0 && ch & mask) {
1721 mask >>= 1;
1722 bit--;
1724 if (bit == 7) {
1725 /* ascii */
1726 u = ch;
1727 } else {
1728 int count;
1730 u = ch & ((1 << bit) - 1);
1731 count = 6 - bit;
1732 while (count) {
1733 key = getch();
1734 if (key == ERR || key == 0)
1735 return;
1737 ch = (unsigned char)key;
1738 u = (u << 6) | (ch & 63);
1739 count--;
1742 handle_ch(u);
1745 static void main_loop(void)
1747 int rc, fd_high;
1749 fd_high = remote_socket;
1750 while (running) {
1751 fd_set set;
1752 struct timeval tv;
1754 update();
1756 FD_ZERO(&set);
1757 FD_SET(0, &set);
1758 FD_SET(remote_socket, &set);
1760 /* Timeout must be so small that screen updates seem instant.
1761 * Only affects changes done in other threads (worker, player).
1763 * Too small timeout makes window updates too fast (wastes CPU).
1765 * Too large timeout makes status line (position) updates too slow.
1766 * The timeout is accuracy of player position.
1768 tv.tv_sec = 0;
1769 tv.tv_usec = 100e3;
1770 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1771 if (rc <= 0) {
1772 if (ctrl_c_pressed) {
1773 handle_ch(0x03);
1774 ctrl_c_pressed = 0;
1776 continue;
1779 if (FD_ISSET(remote_socket, &set)) {
1780 /* no error msgs for cmus-remote */
1781 display_errors = 0;
1783 remote_server_serve();
1785 if (FD_ISSET(0, &set)) {
1786 /* diplay errors for interactive commands */
1787 display_errors = 1;
1789 if (using_utf8) {
1790 u_getch();
1791 } else {
1792 int key = getch();
1794 if (key != ERR && key != 0) {
1795 if (key > 255) {
1796 handle_key(key);
1797 } else {
1798 handle_ch(key);
1806 static int get_next(char **filename)
1808 struct track_info *info;
1810 info = play_queue_remove();
1811 if (info == NULL) {
1812 if (play_library) {
1813 info = lib_set_next();
1814 } else {
1815 info = pl_set_next();
1817 if (info == NULL)
1818 return -1;
1821 *filename = xstrdup(info->filename);
1822 track_info_unref(info);
1823 return 0;
1826 static const struct player_callbacks player_callbacks = {
1827 .get_next = get_next
1830 static void init_curses(void)
1832 struct sigaction act;
1834 sigemptyset(&act.sa_mask);
1835 act.sa_flags = 0;
1836 act.sa_handler = sig_int;
1837 sigaction(SIGINT, &act, NULL);
1839 sigemptyset(&act.sa_mask);
1840 act.sa_flags = 0;
1841 act.sa_handler = sig_winch;
1842 sigaction(SIGWINCH, &act, NULL);
1844 initscr();
1846 /* turn off kb buffering */
1847 cbreak();
1849 keypad(stdscr, TRUE);
1851 /* wait max 5 * 0.1 s if there are no keys available
1852 * doesn't really matter because we use select()
1854 halfdelay(5);
1856 noecho();
1857 if (has_colors()) {
1858 start_color();
1859 use_default_colors();
1861 d_print("Number of supported colors: %d\n", COLORS);
1862 ui_initialized = 1;
1864 /* this was disabled while initializing because it needs to be
1865 * called only once after all colors have been set
1867 update_colors();
1870 static void init_all(void)
1872 char *term;
1874 term = getenv("TERM");
1875 if (term && (strncmp(term, "xterm", 5) == 0 ||
1876 strncmp(term, "rxvt", 4) == 0 ||
1877 strcmp(term, "screen") == 0))
1878 update_window_title = 1;
1880 remote_socket = remote_server_init(server_address);
1882 /* does not select output plugin */
1883 player_init(&player_callbacks);
1885 /* plugins have been loaded so we know what plugin options are available */
1886 options_add();
1888 lib_init();
1889 searchable = tree_searchable;
1890 pl_init();
1891 cmus_init();
1892 browser_init();
1893 filters_init();
1894 cmdline_init();
1895 commands_init();
1896 search_mode_init();
1898 /* almost everything must be initialized now */
1899 options_load();
1901 /* options have been loaded, init plugins (set their options) */
1902 player_init_plugins();
1904 /* finally we can set the output plugin */
1905 player_set_op(output_plugin);
1907 player_get_volume(&player_info.vol_left, &player_info.vol_right, &player_info.vol_max);
1909 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
1910 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
1911 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
1912 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
1914 if (error_count) {
1915 char buf[16];
1917 warn("Press <enter> to continue.");
1918 fgets(buf, sizeof(buf), stdin);
1921 init_curses();
1924 static void exit_all(void)
1926 endwin();
1928 options_exit();
1930 remote_server_exit();
1931 cmus_exit();
1932 cmus_save(lib_for_each, lib_autosave_filename);
1933 cmus_save(pl_for_each, pl_autosave_filename);
1935 player_exit();
1936 lib_exit();
1937 commands_exit();
1938 search_mode_exit();
1939 filters_exit();
1940 browser_exit();
1943 enum {
1944 FLAG_LISTEN,
1945 FLAG_PLUGINS,
1946 FLAG_HELP,
1947 FLAG_VERSION,
1948 NR_FLAGS
1951 static struct option options[NR_FLAGS + 1] = {
1952 { 0, "listen", 1 },
1953 { 0, "plugins", 0 },
1954 { 0, "help", 0 },
1955 { 0, "version", 0 },
1956 { 0, NULL, 0 }
1959 static const char *usage =
1960 "Usage: %s [OPTION]...\n"
1961 "Curses based music player.\n"
1962 "\n"
1963 " --listen ADDR listen ADDR (unix socket) instead of /tmp/cmus-$USER\n"
1964 " --plugins list available plugins and exit\n"
1965 " --help display this help and exit\n"
1966 " --version " VERSION "\n"
1967 "\n"
1968 "Use cmus-remote to control cmus from command line.\n"
1969 "Documentation: " DATADIR "/doc/" PACKAGE "/cmus.html\n"
1970 "Report bugs to <" PACKAGE_BUGREPORT ">.\n";
1972 int main(int argc, char *argv[])
1974 int list_plugins = 0;
1976 program_name = argv[0];
1977 argv++;
1978 while (1) {
1979 int idx;
1980 char *arg;
1982 idx = get_option(&argv, options, &arg);
1983 if (idx < 0)
1984 break;
1986 switch (idx) {
1987 case FLAG_HELP:
1988 printf(usage, program_name);
1989 return 0;
1990 case FLAG_VERSION:
1991 printf(PACKAGE " " VERSION "\nCopyright 2004-2005 Timo Hirvonen\n");
1992 return 0;
1993 case FLAG_PLUGINS:
1994 list_plugins = 1;
1995 break;
1996 case FLAG_LISTEN:
1997 server_address = xstrdup(arg);
1998 break;
2002 setlocale(LC_CTYPE, "");
2003 #ifdef CODESET
2004 charset = nl_langinfo(CODESET);
2005 #else
2006 charset = "ISO-8859-1";
2007 #endif
2008 if (strcmp(charset, "UTF-8") == 0) {
2009 using_utf8 = 1;
2010 } else {
2011 using_utf8 = 0;
2013 misc_init();
2014 if (server_address == NULL) {
2015 server_address = xnew(char, 256);
2016 snprintf(server_address, 256, "/tmp/cmus-%s", user_name);
2018 debug_init();
2019 d_print("charset = '%s'\n", charset);
2021 player_load_plugins();
2022 if (list_plugins) {
2023 player_dump_plugins();
2024 return 0;
2026 init_all();
2027 main_loop();
2028 exit_all();
2029 return 0;