Add struct stat into struct directory, special case for symbolic links
[cmus.git] / ui_curses.c
blobb1047cc2d5afbb84f2412e5ae28a8611166e663f
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/version.h"
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <errno.h>
50 #include <sys/ioctl.h>
51 #include <ctype.h>
52 #include <dirent.h>
53 #include <locale.h>
54 #include <langinfo.h>
55 #include <iconv.h>
56 #include <signal.h>
57 #include <stdarg.h>
59 #if defined(__sun__)
60 /* TIOCGWINSZ */
61 #include <termios.h>
62 #include <ncurses.h>
63 #else
64 #include <curses.h>
65 #endif
67 /* defined in <term.h> but without const */
68 char *tgetstr(const char *id, char **area);
69 char *tgoto(const char *cap, int col, int row);
71 /* globals. documented in ui_curses.h */
73 int ui_initialized = 0;
74 enum ui_input_mode input_mode = NORMAL_MODE;
75 int cur_view = TREE_VIEW;
76 struct searchable *searchable;
78 /* display parse errors? (command line) */
79 int display_errors = 0;
81 char *lib_filename = NULL;
82 char *pl_filename = NULL;
84 /* ------------------------------------------------------------------------- */
86 /* currently playing file */
87 static struct track_info *cur_track_info = NULL;
89 static int running = 1;
90 static char *lib_autosave_filename;
91 static char *pl_autosave_filename;
93 /* shown error message and time stamp
94 * error is cleared if it is older than 3s and key was pressed
96 static char error_buf[512];
97 static time_t error_time = 0;
98 /* info messages are displayed in different color */
99 static int msg_is_error;
100 static int error_count = 0;
102 static char *server_address = NULL;
103 static int remote_socket = -1;
105 static char *charset = NULL;
106 static char print_buffer[512];
108 /* destination buffer for utf8_encode and utf8_decode */
109 static char conv_buffer[512];
111 #define print_buffer_size (sizeof(print_buffer) - 1)
112 static int using_utf8;
114 static const char *t_ts;
115 static const char *t_fs;
117 static int tree_win_x = 0;
118 static int tree_win_y = 0;
119 static int tree_win_w = 0;
121 static int track_win_x = 0;
122 static int track_win_y = 0;
123 static int track_win_w = 0;
125 enum {
126 CURSED_WIN,
127 CURSED_WIN_CUR,
128 CURSED_WIN_SEL,
129 CURSED_WIN_SEL_CUR,
131 CURSED_WIN_ACTIVE,
132 CURSED_WIN_ACTIVE_CUR,
133 CURSED_WIN_ACTIVE_SEL,
134 CURSED_WIN_ACTIVE_SEL_CUR,
136 CURSED_SEPARATOR,
137 CURSED_WIN_TITLE,
138 CURSED_COMMANDLINE,
139 CURSED_STATUSLINE,
141 CURSED_TITLELINE,
142 CURSED_DIR,
143 CURSED_ERROR,
144 CURSED_INFO,
146 NR_CURSED
149 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
150 COLOR_WIN_BG,
151 COLOR_WIN_BG,
152 COLOR_WIN_INACTIVE_SEL_BG,
153 COLOR_WIN_INACTIVE_CUR_SEL_BG,
155 COLOR_WIN_BG,
156 COLOR_WIN_BG,
157 COLOR_WIN_SEL_BG,
158 COLOR_WIN_CUR_SEL_BG,
160 COLOR_WIN_BG,
161 COLOR_WIN_TITLE_BG,
162 COLOR_CMDLINE_BG,
163 COLOR_STATUSLINE_BG,
165 COLOR_TITLELINE_BG,
166 COLOR_WIN_BG,
167 COLOR_CMDLINE_BG,
168 COLOR_CMDLINE_BG
171 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
172 COLOR_WIN_FG,
173 COLOR_WIN_CUR,
174 COLOR_WIN_INACTIVE_SEL_FG,
175 COLOR_WIN_INACTIVE_CUR_SEL_FG,
177 COLOR_WIN_FG,
178 COLOR_WIN_CUR,
179 COLOR_WIN_SEL_FG,
180 COLOR_WIN_CUR_SEL_FG,
182 COLOR_SEPARATOR,
183 COLOR_WIN_TITLE_FG,
184 COLOR_CMDLINE_FG,
185 COLOR_STATUSLINE_FG,
187 COLOR_TITLELINE_FG,
188 COLOR_WIN_DIR,
189 COLOR_ERROR,
190 COLOR_INFO
193 /* index is CURSED_*, value is fucking color pair */
194 static int pairs[NR_CURSED];
196 enum {
197 TF_ARTIST,
198 TF_ALBUM,
199 TF_DISC,
200 TF_TRACK,
201 TF_TITLE,
202 TF_YEAR,
203 TF_GENRE,
204 TF_DURATION,
205 TF_PATHFILE,
206 TF_FILE,
207 NR_TFS
210 static struct format_option track_fopts[NR_TFS + 1] = {
211 DEF_FO_STR('a'),
212 DEF_FO_STR('l'),
213 DEF_FO_INT('D'),
214 DEF_FO_INT('n'),
215 DEF_FO_STR('t'),
216 DEF_FO_STR('y'),
217 DEF_FO_STR('g'),
218 DEF_FO_TIME('d'),
219 DEF_FO_STR('f'),
220 DEF_FO_STR('F'),
221 DEF_FO_END
224 enum {
225 SF_STATUS,
226 SF_POSITION,
227 SF_DURATION,
228 SF_TOTAL,
229 SF_VOLUME,
230 SF_LVOLUME,
231 SF_RVOLUME,
232 SF_BUFFER,
233 SF_REPEAT,
234 SF_CONTINUE,
235 SF_SHUFFLE,
236 SF_PLAYLISTMODE,
237 NR_SFS
240 static struct format_option status_fopts[NR_SFS + 1] = {
241 DEF_FO_STR('s'),
242 DEF_FO_TIME('p'),
243 DEF_FO_TIME('d'),
244 DEF_FO_TIME('t'),
245 DEF_FO_INT('v'),
246 DEF_FO_INT('l'),
247 DEF_FO_INT('r'),
248 DEF_FO_INT('b'),
249 DEF_FO_STR('R'),
250 DEF_FO_STR('C'),
251 DEF_FO_STR('S'),
252 DEF_FO_STR('L'),
253 DEF_FO_END
256 static void utf8_encode(const char *buffer)
258 static iconv_t cd = (iconv_t)-1;
259 size_t is, os;
260 const char *i;
261 char *o;
262 int rc;
264 if (cd == (iconv_t)-1) {
265 d_print("iconv_open(UTF-8, %s)\n", charset);
266 cd = iconv_open("UTF-8", charset);
267 if (cd == (iconv_t)-1) {
268 d_print("iconv_open failed: %s\n", strerror(errno));
269 return;
272 i = buffer;
273 o = conv_buffer;
274 is = strlen(i);
275 os = sizeof(conv_buffer) - 1;
276 rc = iconv(cd, (void *)&i, &is, &o, &os);
277 *o = 0;
278 if (rc == -1) {
279 d_print("iconv failed: %s\n", strerror(errno));
280 return;
284 static void utf8_decode(const char *buffer)
286 static iconv_t cd = (iconv_t)-1;
287 size_t is, os;
288 const char *i;
289 char *o;
290 int rc;
292 if (cd == (iconv_t)-1) {
293 d_print("iconv_open(%s, UTF-8)\n", charset);
294 cd = iconv_open(charset, "UTF-8");
295 if (cd == (iconv_t)-1) {
296 d_print("iconv_open failed: %s\n", strerror(errno));
297 return;
300 i = buffer;
301 o = conv_buffer;
302 is = strlen(i);
303 os = sizeof(conv_buffer) - 1;
304 rc = iconv(cd, (void *)&i, &is, &o, &os);
305 *o = 0;
306 if (rc == -1) {
307 d_print("iconv failed: %s\n", strerror(errno));
308 return;
312 /* screen updates {{{ */
314 static void dump_print_buffer(int row, int col)
316 if (using_utf8) {
317 mvaddstr(row, col, print_buffer);
318 } else {
319 utf8_decode(print_buffer);
320 mvaddstr(row, col, conv_buffer);
324 static void sprint(int row, int col, const char *str, int width, int indent)
326 int s, d, ellipsis_pos = 0, cut_double_width = 0;
328 width -= 2 + indent;
329 d = indent + 1;
330 memset(print_buffer, ' ', d);
331 s = 0;
332 while (1) {
333 uchar u;
334 int w;
336 if (width == 3)
337 ellipsis_pos = d;
339 u_get_char(str, &s, &u);
340 if (u == 0) {
341 memset(print_buffer + d, ' ', width);
342 d += width;
343 break;
346 w = u_char_width(u);
347 if (width == 3)
348 ellipsis_pos = d;
349 if (width == 4 && w == 2) {
350 /* can't cut double-width char */
351 ellipsis_pos = d + 1;
352 cut_double_width = 1;
355 width -= w;
356 if (width < 0) {
357 /* does not fit */
358 d = ellipsis_pos;
359 if (cut_double_width) {
360 /* first half of the double-width char */
361 print_buffer[d - 1] = ' ';
363 print_buffer[d++] = '.';
364 print_buffer[d++] = '.';
365 print_buffer[d++] = '.';
366 break;
368 u_set_char(print_buffer, &d, u);
370 print_buffer[d++] = ' ';
371 print_buffer[d++] = 0;
372 dump_print_buffer(row, col);
375 static void sprint_ascii(int row, int col, const char *str, int len)
377 int l;
379 l = strlen(str);
380 len -= 2;
382 print_buffer[0] = ' ';
383 if (l > len) {
384 memcpy(print_buffer + 1, str, len - 3);
385 print_buffer[len - 2] = '.';
386 print_buffer[len - 1] = '.';
387 print_buffer[len - 0] = '.';
388 } else {
389 memcpy(print_buffer + 1, str, l);
390 memset(print_buffer + 1 + l, ' ', len - l);
392 print_buffer[len + 1] = ' ';
393 print_buffer[len + 2] = 0;
394 mvaddstr(row, col, print_buffer);
397 static void print_tree(struct window *win, int row, struct iter *iter)
399 const char *noname = "<no name>";
400 struct artist *artist;
401 struct album *album;
402 struct iter sel;
403 int current, selected, active;
405 artist = iter_to_artist(iter);
406 album = iter_to_album(iter);
407 current = 0;
408 if (lib_cur_track) {
409 if (album) {
410 current = CUR_ALBUM == album;
411 } else {
412 current = CUR_ARTIST == artist;
415 window_get_sel(win, &sel);
416 selected = iters_equal(iter, &sel);
417 active = lib_cur_win == lib_tree_win;
418 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
419 if (album) {
420 sprint(tree_win_y + row + 1, tree_win_x, album->name ? : noname, tree_win_w, 2);
421 } else {
422 sprint(tree_win_y + row + 1, tree_win_x, artist->name ? : noname, tree_win_w, 0);
426 static inline void fopt_set_str(struct format_option *fopt, const char *str)
428 BUG_ON(fopt->type != FO_STR);
429 if (str) {
430 fopt->fo_str = str;
431 fopt->empty = 0;
432 } else {
433 fopt->empty = 1;
437 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
439 BUG_ON(fopt->type != FO_INT);
440 fopt->fo_int = value;
441 fopt->empty = empty;
444 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
446 BUG_ON(fopt->type != FO_TIME);
447 fopt->fo_time = value;
448 fopt->empty = empty;
451 static void fill_track_fopts(struct tree_track *track)
453 const char *filename;
454 const struct track_info *ti = tree_track_info(track);
455 int num, disc;
457 if (using_utf8) {
458 filename = ti->filename;
459 } else {
460 utf8_encode(ti->filename);
461 filename = conv_buffer;
463 disc = track->shuffle_track.simple_track.disc;
464 num = track->shuffle_track.simple_track.num;
466 fopt_set_str(&track_fopts[TF_ARTIST], track->album->artist->name);
467 fopt_set_str(&track_fopts[TF_ALBUM], track->album->name);
468 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
469 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
470 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(ti->comments, "title"));
471 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(ti->comments, "date"));
472 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(ti->comments, "genre"));
473 fopt_set_time(&track_fopts[TF_DURATION], ti->duration, ti->duration == -1);
474 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
475 if (is_url(ti->filename)) {
476 fopt_set_str(&track_fopts[TF_FILE], filename);
477 } else {
478 const char *f;
480 f = strrchr(filename, '/');
481 if (f) {
482 fopt_set_str(&track_fopts[TF_FILE], f + 1);
483 } else {
484 fopt_set_str(&track_fopts[TF_FILE], filename);
489 static void fill_track_fopts_track_info(struct track_info *info)
491 char *filename;
492 int num, disc;
494 if (using_utf8) {
495 filename = info->filename;
496 } else {
497 utf8_encode(info->filename);
498 filename = conv_buffer;
500 disc = comments_get_int(info->comments, "discnumber");
501 num = comments_get_int(info->comments, "tracknumber");
503 fopt_set_str(&track_fopts[TF_ARTIST], comments_get_val(info->comments, "artist"));
504 fopt_set_str(&track_fopts[TF_ALBUM], comments_get_val(info->comments, "album"));
505 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
506 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
507 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(info->comments, "title"));
508 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(info->comments, "date"));
509 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(info->comments, "genre"));
510 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
511 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
512 if (is_url(info->filename)) {
513 fopt_set_str(&track_fopts[TF_FILE], filename);
514 } else {
515 const char *f;
517 f = strrchr(filename, '/');
518 if (f) {
519 fopt_set_str(&track_fopts[TF_FILE], f + 1);
520 } else {
521 fopt_set_str(&track_fopts[TF_FILE], filename);
526 static void print_track(struct window *win, int row, struct iter *iter)
528 struct tree_track *track;
529 struct iter sel;
530 int current, selected, active;
532 track = iter_to_tree_track(iter);
533 current = lib_cur_track == track;
534 window_get_sel(win, &sel);
535 selected = iters_equal(iter, &sel);
536 active = lib_cur_win == lib_track_win;
537 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
539 fill_track_fopts(track);
541 if (track_info_has_tag(tree_track_info(track))) {
542 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
543 } else {
544 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
546 dump_print_buffer(track_win_y + row + 1, track_win_x);
549 /* used by print_editable only */
550 static struct simple_track *current_track;
552 static void print_editable(struct window *win, int row, struct iter *iter)
554 struct simple_track *track;
555 struct iter sel;
556 int current, selected, active;
558 track = iter_to_simple_track(iter);
559 current = current_track == track;
560 window_get_sel(win, &sel);
561 selected = iters_equal(iter, &sel);
563 active = 1;
564 if (!selected && track->marked) {
565 selected = 1;
566 active = 0;
569 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
571 fill_track_fopts_track_info(track->info);
573 if (track_info_has_tag(track->info)) {
574 format_print(print_buffer, COLS, list_win_format, track_fopts);
575 } else {
576 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
578 dump_print_buffer(row + 1, 0);
581 static void print_browser(struct window *win, int row, struct iter *iter)
583 struct browser_entry *e;
584 struct iter sel;
585 int selected;
587 e = iter_to_browser_entry(iter);
588 window_get_sel(win, &sel);
589 selected = iters_equal(iter, &sel);
590 if (selected) {
591 int active = 1;
592 int current = 0;
594 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
595 } else {
596 if (e->type == BROWSER_ENTRY_DIR) {
597 bkgdset(pairs[CURSED_DIR]);
598 } else {
599 bkgdset(pairs[CURSED_WIN]);
603 /* file name encoding == terminal encoding. no need to convert */
604 if (using_utf8) {
605 sprint(row + 1, 0, e->name, COLS, 0);
606 } else {
607 sprint_ascii(row + 1, 0, e->name, COLS);
611 static void print_filter(struct window *win, int row, struct iter *iter)
613 char buf[256];
614 struct filter_entry *e = iter_to_filter_entry(iter);
615 struct iter sel;
616 /* window active? */
617 int active = 1;
618 /* row selected? */
619 int selected;
620 /* is the filter currently active? */
621 int current = e->active;
623 window_get_sel(win, &sel);
624 selected = iters_equal(iter, &sel);
625 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
627 snprintf(buf, sizeof(buf), "%c %-15s %s", e->selected ? '*' : ' ', e->name, e->filter);
628 sprint(row + 1, 0, buf, COLS, 0);
631 static void update_window(struct window *win, int x, int y, int w, const char *title,
632 void (*print)(struct window *, int, struct iter *))
634 struct iter iter;
635 int nr_rows;
636 int c, i;
638 win->changed = 0;
640 bkgdset(pairs[CURSED_WIN_TITLE]);
641 c = snprintf(print_buffer, w + 1, " %s", title);
642 if (c > w)
643 c = w;
644 memset(print_buffer + c, ' ', w - c + 1);
645 print_buffer[w] = 0;
646 dump_print_buffer(y, x);
647 nr_rows = window_get_nr_rows(win);
648 i = 0;
649 if (window_get_top(win, &iter)) {
650 while (i < nr_rows) {
651 print(win, i, &iter);
652 i++;
653 if (!window_get_next(win, &iter))
654 break;
658 bkgdset(pairs[0]);
659 memset(print_buffer, ' ', w);
660 print_buffer[w] = 0;
661 while (i < nr_rows) {
662 dump_print_buffer(y + i + 1, x);
663 i++;
667 static void update_tree_window(void)
669 update_window(lib_tree_win, tree_win_x, tree_win_y,
670 tree_win_w, "Artist / Album", print_tree);
673 static void update_track_window(void)
675 char title[512];
677 /* it doesn't matter what format options we use because the format
678 * string does not contain any format charaters */
679 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
680 update_window(lib_track_win, track_win_x, track_win_y,
681 track_win_w, title, print_track);
684 static const char *pretty(const char *path)
686 static int home_len = -1;
687 static char buf[256];
689 if (home_len == -1)
690 home_len = strlen(home_dir);
692 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
693 return path;
695 buf[0] = '~';
696 strcpy(buf + 1, path + home_len);
697 return buf;
700 static const char * const sorted_names[2] = { "", "sorted by " };
702 static void update_editable_window(struct editable *e, const char *title, const char *filename)
704 char buf[512];
705 int pos;
707 if (filename) {
708 if (using_utf8) {
709 /* already UTF-8 */
710 } else {
711 utf8_encode(filename);
712 filename = conv_buffer;
714 snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
715 pretty(filename), e->nr_tracks);
716 } else {
717 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
720 if (e->nr_marked) {
721 pos = strlen(buf);
722 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
724 pos = strlen(buf);
725 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
726 sorted_names[e->sort_str[0] != 0], e->sort_str);
728 update_window(e->win, 0, 0, COLS, buf, &print_editable);
731 static void update_sorted_window(void)
733 current_track = (struct simple_track *)lib_cur_track;
734 update_editable_window(&lib_editable, "Library", lib_filename);
737 static void update_pl_window(void)
739 current_track = pl_cur_track;
740 update_editable_window(&pl_editable, "Playlist", pl_filename);
743 static void update_play_queue_window(void)
745 current_track = NULL;
746 update_editable_window(&pq_editable, "Play Queue", NULL);
749 static void update_browser_window(void)
751 char title[512];
752 char *dirname;
754 if (using_utf8) {
755 /* already UTF-8 */
756 dirname = browser_dir;
757 } else {
758 utf8_encode(browser_dir);
759 dirname = conv_buffer;
761 snprintf(title, sizeof(title), "Browser - %s", dirname);
762 update_window(browser_win, 0, 0, COLS, title, print_browser);
765 static void update_filters_window(void)
767 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
770 static void draw_separator(void)
772 int row;
774 bkgdset(pairs[CURSED_WIN_TITLE]);
775 mvaddch(0, tree_win_w, ' ');
776 bkgdset(pairs[CURSED_SEPARATOR]);
777 for (row = 1; row < LINES - 3; row++)
778 mvaddch(row, tree_win_w, ACS_VLINE);
781 static void do_update_view(int full)
783 switch (cur_view) {
784 case TREE_VIEW:
785 editable_lock();
786 if (full || lib_tree_win->changed)
787 update_tree_window();
788 if (full || lib_track_win->changed)
789 update_track_window();
790 editable_unlock();
791 draw_separator();
792 break;
793 case SORTED_VIEW:
794 editable_lock();
795 update_sorted_window();
796 editable_unlock();
797 break;
798 case PLAYLIST_VIEW:
799 editable_lock();
800 update_pl_window();
801 editable_unlock();
802 break;
803 case QUEUE_VIEW:
804 editable_lock();
805 update_play_queue_window();
806 editable_unlock();
807 break;
808 case BROWSER_VIEW:
809 update_browser_window();
810 break;
811 case FILTERS_VIEW:
812 update_filters_window();
813 break;
817 static void do_update_statusline(void)
819 static const char *status_strs[] = { ".", ">", "|" };
820 static const char *cont_strs[] = { " ", "C" };
821 static const char *repeat_strs[] = { " ", "R" };
822 static const char *shuffle_strs[] = { " ", "S" };
823 int buffer_fill, vol, vol_left, vol_right;
824 int duration = -1;
825 char *msg;
826 char format[80];
828 editable_lock();
829 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
830 pl_editable.total_time, 0);
831 editable_unlock();
833 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
834 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
835 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
837 if (cur_track_info)
838 duration = cur_track_info->duration;
840 player_info_lock();
842 if (volume_max == 0) {
843 vol_left = vol_right = vol = -1;
844 } else {
845 vol_left = scale_to_percentage(player_info.vol_left, volume_max);
846 vol_right = scale_to_percentage(player_info.vol_right, volume_max);
847 vol = (vol_left + vol_right + 1) / 2;
849 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
851 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
853 if (show_remaining_time && duration != -1) {
854 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
855 } else {
856 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
859 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
860 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
861 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
862 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
863 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
864 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
866 strcpy(format, " %s %p ");
867 if (duration != -1)
868 strcat(format, "/ %d ");
869 strcat(format, "- %t ");
870 if (volume_max != 0) {
871 if (player_info.vol_left != player_info.vol_right) {
872 strcat(format, "vol: %l,%r ");
873 } else {
874 strcat(format, "vol: %v ");
877 if (cur_track_info && is_url(cur_track_info->filename))
878 strcat(format, "buf: %b ");
879 strcat(format, "%=");
880 if (play_library) {
881 /* artist/album modes work only in lib */
882 if (shuffle) {
883 /* shuffle overrides sorted mode */
884 strcat(format, "%L from library");
885 } else if (play_sorted) {
886 strcat(format, "%L from sorted library");
887 } else {
888 strcat(format, "%L from library");
890 } else {
891 strcat(format, "playlist");
893 strcat(format, " | %1C%1R%1S ");
894 format_print(print_buffer, COLS, format, status_fopts);
896 msg = player_info.error_msg;
897 player_info.error_msg = NULL;
899 player_info_unlock();
901 bkgdset(pairs[CURSED_STATUSLINE]);
902 dump_print_buffer(LINES - 2, 0);
904 if (msg) {
905 error_msg("%s", msg);
906 free(msg);
910 static void dump_buffer(const char *buffer)
912 if (using_utf8) {
913 addstr(buffer);
914 } else {
915 utf8_decode(buffer);
916 addstr(conv_buffer);
920 static void do_update_commandline(void)
922 char *str;
923 int w, idx;
924 char ch;
926 move(LINES - 1, 0);
927 if (error_buf[0]) {
928 if (msg_is_error) {
929 bkgdset(pairs[CURSED_ERROR]);
930 } else {
931 bkgdset(pairs[CURSED_INFO]);
933 addstr(error_buf);
934 clrtoeol();
935 return;
937 bkgdset(pairs[CURSED_COMMANDLINE]);
938 if (input_mode == NORMAL_MODE) {
939 clrtoeol();
940 return;
943 str = cmdline.line;
944 if (!using_utf8) {
945 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
946 * characters are invalid UTF-8 so it really is in locale's
947 * encoding.
949 * This code should be safe because cmdline.bpos ==
950 * cmdline.cpos as every non-ASCII character is counted as one
951 * invalid UTF-8 byte.
953 * NOTE: This has nothing to do with widths of printed
954 * characters. I.e. even if there were control characters
955 * (displayed as <xx>) there would be no problem because bpos
956 * still equals to cpos, I think.
958 utf8_encode(cmdline.line);
959 str = conv_buffer;
962 /* COMMAND_MODE or SEARCH_MODE */
963 w = u_str_width(str);
964 ch = ':';
965 if (input_mode == SEARCH_MODE)
966 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
968 if (w <= COLS - 2) {
969 addch(ch);
970 idx = u_copy_chars(print_buffer, str, &w);
971 print_buffer[idx] = 0;
972 dump_buffer(print_buffer);
973 clrtoeol();
974 } else {
975 /* keep cursor as far right as possible */
976 int skip, width, cw;
978 /* cursor pos (width, not chars. doesn't count the ':') */
979 cw = u_str_nwidth(str, cmdline.cpos);
981 skip = cw + 2 - COLS;
982 if (skip > 0) {
983 /* skip the ':' */
984 skip--;
986 /* skip rest (if any) */
987 idx = u_skip_chars(str, &skip);
989 width = COLS;
990 idx = u_copy_chars(print_buffer, str + idx, &width);
991 while (width < COLS) {
992 /* cursor is at end of the buffer
993 * print 1, 2 or 3 spaces
995 * To clarify:
997 * If the last _skipped_ character was double-width we may need
998 * to print 2 spaces.
1000 * If the last _skipped_ character was invalid UTF-8 we may need
1001 * to print 3 spaces.
1003 print_buffer[idx++] = ' ';
1004 width++;
1006 print_buffer[idx] = 0;
1007 dump_buffer(print_buffer);
1008 } else {
1009 /* print ':' + COLS - 1 chars */
1010 addch(ch);
1011 width = COLS - 1;
1012 idx = u_copy_chars(print_buffer, str, &width);
1013 print_buffer[idx] = 0;
1014 dump_buffer(print_buffer);
1019 /* lock player_info! */
1020 static const char *get_stream_title(void)
1022 static char stream_title[255 * 16 + 1];
1023 char *ptr, *title;
1025 ptr = strstr(player_info.metadata, "StreamTitle='");
1026 if (ptr == NULL)
1027 return NULL;
1028 ptr += 13;
1029 title = ptr;
1030 while (*ptr) {
1031 if (*ptr == '\'' && *(ptr + 1) == ';') {
1032 memcpy(stream_title, title, ptr - title);
1033 stream_title[ptr - title] = 0;
1034 return stream_title;
1036 ptr++;
1038 return NULL;
1041 static void set_title(const char *title)
1043 if (t_ts) {
1044 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1045 fflush(stdout);
1049 static void do_update_titleline(void)
1051 bkgdset(pairs[CURSED_TITLELINE]);
1052 player_info_lock();
1053 if (cur_track_info) {
1054 int i, use_alt_format = 0;
1055 char *wtitle;
1057 fill_track_fopts_track_info(cur_track_info);
1058 if (is_url(cur_track_info->filename)) {
1059 const char *title = get_stream_title();
1061 if (title == NULL)
1062 use_alt_format = 1;
1063 fopt_set_str(&track_fopts[TF_TITLE], title);
1064 } else {
1065 use_alt_format = !track_info_has_tag(cur_track_info);
1068 if (use_alt_format) {
1069 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1070 } else {
1071 format_print(print_buffer, COLS, current_format, track_fopts);
1073 dump_print_buffer(LINES - 3, 0);
1075 /* set window title */
1076 if (use_alt_format) {
1077 format_print(print_buffer, sizeof(print_buffer) - 1,
1078 window_title_alt_format, track_fopts);
1079 } else {
1080 format_print(print_buffer, sizeof(print_buffer) - 1,
1081 window_title_format, track_fopts);
1084 /* remove whitespace */
1085 i = strlen(print_buffer) - 1;
1086 while (i > 0 && print_buffer[i] == ' ')
1087 i--;
1088 print_buffer[i + 1] = 0;
1090 if (using_utf8) {
1091 wtitle = print_buffer;
1092 } else {
1093 utf8_decode(print_buffer);
1094 wtitle = conv_buffer;
1097 set_title(wtitle);
1098 } else {
1099 move(LINES - 3, 0);
1100 clrtoeol();
1102 set_title("cmus " VERSION);
1104 player_info_unlock();
1107 static int cmdline_cursor_column(void)
1109 char *str;
1110 int cw, skip, s;
1112 str = cmdline.line;
1113 if (!using_utf8) {
1114 /* see do_update_commandline */
1115 utf8_encode(cmdline.line);
1116 str = conv_buffer;
1119 /* width of the text in the buffer before cursor */
1120 cw = u_str_nwidth(str, cmdline.cpos);
1122 if (1 + cw < COLS) {
1123 /* whole line is visible */
1124 return 1 + cw;
1127 /* beginning of cmdline is not visible */
1129 /* check if the first visible char in cmdline would be halved
1130 * double-width character (or invalid byte <xx>) which is not possible.
1131 * we need to skip the whole character and move cursor to COLS - 2
1132 * column. */
1133 skip = cw + 2 - COLS;
1135 /* skip the ':' */
1136 skip--;
1138 /* skip rest */
1139 s = skip;
1140 u_skip_chars(str, &s);
1141 if (s > skip) {
1142 /* the last skipped char was double-width or <xx> */
1143 return COLS - 1 - (s - skip);
1145 return COLS - 1;
1148 static void post_update(void)
1150 /* refresh makes cursor visible at least for urxvt */
1151 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1152 move(LINES - 1, cmdline_cursor_column());
1153 refresh();
1154 curs_set(1);
1155 } else {
1156 move(LINES - 1, 0);
1157 refresh();
1158 curs_set(0);
1162 void update_titleline(void)
1164 curs_set(0);
1165 do_update_titleline();
1166 post_update();
1169 void update_full(void)
1171 if (!ui_initialized)
1172 return;
1174 curs_set(0);
1176 do_update_view(1);
1177 do_update_titleline();
1178 do_update_statusline();
1179 do_update_commandline();
1181 post_update();
1184 static void update_commandline(void)
1186 curs_set(0);
1187 do_update_commandline();
1188 post_update();
1191 void update_statusline(void)
1193 if (!ui_initialized)
1194 return;
1196 curs_set(0);
1197 do_update_statusline();
1198 post_update();
1201 void info_msg(const char *format, ...)
1203 va_list ap;
1205 va_start(ap, format);
1206 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1207 va_end(ap);
1209 msg_is_error = 0;
1211 update_commandline();
1214 void error_msg(const char *format, ...)
1216 va_list ap;
1218 if (!display_errors)
1219 return;
1221 strcpy(error_buf, "Error: ");
1222 va_start(ap, format);
1223 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1224 va_end(ap);
1226 msg_is_error = 1;
1227 error_count++;
1229 if (ui_initialized) {
1230 error_time = time(NULL);
1231 update_commandline();
1232 } else {
1233 warn("%s\n", error_buf);
1234 error_buf[0] = 0;
1238 int yes_no_query(const char *format, ...)
1240 char buffer[512];
1241 va_list ap;
1242 int ret = 0;
1244 va_start(ap, format);
1245 vsnprintf(buffer, sizeof(buffer), format, ap);
1246 va_end(ap);
1248 move(LINES - 1, 0);
1249 bkgdset(pairs[CURSED_INFO]);
1251 /* no need to convert buffer.
1252 * it is always encoded in the right charset (assuming filenames are
1253 * encoded in same charset as LC_CTYPE).
1256 addstr(buffer);
1257 clrtoeol();
1258 refresh();
1260 while (1) {
1261 int ch = getch();
1263 if (ch == ERR || ch == 0)
1264 continue;
1265 if (ch == 'y')
1266 ret = 1;
1267 break;
1269 update_commandline();
1270 return ret;
1273 void search_not_found(void)
1275 const char *what = "Track";
1277 if (search_restricted) {
1278 switch (cur_view) {
1279 case TREE_VIEW:
1280 what = "Artist/album";
1281 break;
1282 case SORTED_VIEW:
1283 case PLAYLIST_VIEW:
1284 case QUEUE_VIEW:
1285 what = "Title";
1286 break;
1287 case BROWSER_VIEW:
1288 what = "File/Directory";
1289 break;
1290 case FILTERS_VIEW:
1291 what = "Filter";
1292 break;
1294 } else {
1295 switch (cur_view) {
1296 case TREE_VIEW:
1297 case SORTED_VIEW:
1298 case PLAYLIST_VIEW:
1299 case QUEUE_VIEW:
1300 what = "Track";
1301 break;
1302 case BROWSER_VIEW:
1303 what = "File/Directory";
1304 break;
1305 case FILTERS_VIEW:
1306 what = "Filter";
1307 break;
1310 info_msg("%s not found: %s", what, search_str ? : "");
1313 void set_view(int view)
1315 if (view == cur_view)
1316 return;
1317 cur_view = view;
1319 editable_lock();
1320 switch (cur_view) {
1321 case TREE_VIEW:
1322 searchable = tree_searchable;
1323 update_tree_window();
1324 update_track_window();
1325 draw_separator();
1326 break;
1327 case SORTED_VIEW:
1328 searchable = lib_editable.searchable;
1329 update_sorted_window();
1330 break;
1331 case PLAYLIST_VIEW:
1332 searchable = pl_editable.searchable;
1333 update_pl_window();
1334 break;
1335 case QUEUE_VIEW:
1336 searchable = pq_editable.searchable;
1337 update_play_queue_window();
1338 break;
1339 case BROWSER_VIEW:
1340 searchable = browser_searchable;
1341 update_browser_window();
1342 break;
1343 case FILTERS_VIEW:
1344 searchable = filters_searchable;
1345 update_filters_window();
1346 break;
1348 editable_unlock();
1350 refresh();
1353 void enter_command_mode(void)
1355 error_buf[0] = 0;
1356 error_time = 0;
1357 input_mode = COMMAND_MODE;
1358 update_commandline();
1361 void enter_search_mode(void)
1363 error_buf[0] = 0;
1364 error_time = 0;
1365 input_mode = SEARCH_MODE;
1366 search_direction = SEARCH_FORWARD;
1367 update_commandline();
1370 void enter_search_backward_mode(void)
1372 error_buf[0] = 0;
1373 error_time = 0;
1374 input_mode = SEARCH_MODE;
1375 search_direction = SEARCH_BACKWARD;
1376 update_commandline();
1379 void quit(void)
1381 running = 0;
1384 void update_colors(void)
1386 int i;
1388 if (!ui_initialized)
1389 return;
1391 for (i = 0; i < NR_CURSED; i++) {
1392 int bg = colors[cursed_to_bg_idx[i]];
1393 int fg = colors[cursed_to_fg_idx[i]];
1394 int pair = i + 1;
1396 if (fg >= 8 && fg <= 15) {
1397 /* fg colors 8..15 are special (0..7 + bold) */
1398 init_pair(pair, fg & 7, bg);
1399 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1400 } else {
1401 init_pair(pair, fg, bg);
1402 pairs[i] = COLOR_PAIR(pair);
1407 static void clear_error(void)
1409 time_t t = time(NULL);
1411 /* error msg is visible at least 3s */
1412 if (t - error_time < 3)
1413 return;
1415 if (error_buf[0]) {
1416 error_time = 0;
1417 error_buf[0] = 0;
1418 update_commandline();
1422 /* screen updates }}} */
1424 static void spawn_status_program(void)
1426 static const char *status_strs[] = { "stopped", "playing", "paused" };
1427 const char *stream_title = NULL;
1428 char *argv[32];
1429 int i, status;
1431 if (status_display_program == NULL || status_display_program[0] == 0)
1432 return;
1434 player_info_lock();
1435 status = player_info.status;
1436 if (status == 1)
1437 stream_title = get_stream_title();
1438 player_info_unlock();
1440 i = 0;
1441 argv[i++] = xstrdup(status_display_program);
1443 argv[i++] = xstrdup("status");
1444 argv[i++] = xstrdup(status_strs[status]);
1445 if (cur_track_info) {
1446 static const char *keys[] = {
1447 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1449 int j;
1451 if (is_url(cur_track_info->filename)) {
1452 argv[i++] = xstrdup("url");
1453 argv[i++] = xstrdup(cur_track_info->filename);
1454 if (stream_title) {
1455 argv[i++] = xstrdup("title");
1456 argv[i++] = xstrdup(stream_title);
1458 } else {
1459 char buf[32];
1461 argv[i++] = xstrdup("file");
1462 argv[i++] = xstrdup(cur_track_info->filename);
1463 for (j = 0; keys[j]; j++) {
1464 const char *key = keys[j];
1465 const char *val;
1467 val = comments_get_val(cur_track_info->comments, key);
1468 if (val) {
1469 argv[i++] = xstrdup(key);
1470 argv[i++] = xstrdup(val);
1473 snprintf(buf, sizeof(buf), "%d", cur_track_info->duration);
1474 argv[i++] = xstrdup("duration");
1475 argv[i++] = xstrdup(buf);
1478 argv[i++] = NULL;
1479 if (spawn(argv, &status) == -1)
1480 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1481 for (i = 0; argv[i]; i++)
1482 free(argv[i]);
1485 static int ctrl_c_pressed = 0;
1487 static void sig_int(int sig)
1489 ctrl_c_pressed = 1;
1492 static int needs_to_resize = 1;
1494 static void sig_winch(int sig)
1496 needs_to_resize = 1;
1499 static int get_window_size(int *lines, int *columns)
1501 struct winsize ws;
1503 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1504 return -1;
1505 *columns = ws.ws_col;
1506 *lines = ws.ws_row;
1507 return 0;
1510 static void resize_tree_view(int w, int h)
1512 tree_win_w = w / 3;
1513 track_win_w = w - tree_win_w - 1;
1514 if (tree_win_w < 8)
1515 tree_win_w = 8;
1516 if (track_win_w < 8)
1517 track_win_w = 8;
1518 tree_win_x = 0;
1519 tree_win_y = 0;
1520 track_win_x = tree_win_w + 1;
1521 track_win_y = 0;
1523 h--;
1524 window_set_nr_rows(lib_tree_win, h);
1525 window_set_nr_rows(lib_track_win, h);
1528 static void update(void)
1530 int needs_view_update = 0;
1531 int needs_title_update = 0;
1532 int needs_status_update = 0;
1533 int needs_command_update = 0;
1534 int needs_spawn = 0;
1536 if (needs_to_resize) {
1537 int w, h;
1538 int columns, lines;
1540 if (get_window_size(&lines, &columns) == 0) {
1541 needs_to_resize = 0;
1542 resizeterm(lines, columns);
1543 w = COLS;
1544 h = LINES - 3;
1545 if (w < 16)
1546 w = 16;
1547 if (h < 8)
1548 h = 8;
1549 editable_lock();
1550 resize_tree_view(w, h);
1551 window_set_nr_rows(lib_editable.win, h - 1);
1552 window_set_nr_rows(pl_editable.win, h - 1);
1553 window_set_nr_rows(pq_editable.win, h - 1);
1554 window_set_nr_rows(filters_win, h - 1);
1555 window_set_nr_rows(browser_win, h - 1);
1556 editable_unlock();
1557 needs_title_update = 1;
1558 needs_status_update = 1;
1559 needs_command_update = 1;
1563 player_info_lock();
1564 editable_lock();
1566 needs_spawn = player_info.status_changed || player_info.file_changed ||
1567 player_info.metadata_changed;
1569 if (player_info.file_changed) {
1570 if (cur_track_info)
1571 track_info_unref(cur_track_info);
1572 if (player_info.filename[0] == 0) {
1573 cur_track_info = NULL;
1574 } else {
1575 cur_track_info = cmus_get_track_info(player_info.filename);
1577 player_info.file_changed = 0;
1578 needs_title_update = 1;
1579 needs_status_update = 1;
1581 if (player_info.metadata_changed) {
1582 player_info.metadata_changed = 0;
1583 needs_title_update = 1;
1585 if (player_info.position_changed || player_info.status_changed || player_info.vol_changed) {
1586 player_info.position_changed = 0;
1587 player_info.status_changed = 0;
1588 player_info.vol_changed = 0;
1590 needs_status_update = 1;
1592 switch (cur_view) {
1593 case TREE_VIEW:
1594 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
1595 break;
1596 case SORTED_VIEW:
1597 needs_view_update += lib_editable.win->changed;
1598 break;
1599 case PLAYLIST_VIEW:
1600 needs_view_update += pl_editable.win->changed;
1601 break;
1602 case QUEUE_VIEW:
1603 needs_view_update += pq_editable.win->changed;
1604 break;
1605 case BROWSER_VIEW:
1606 needs_view_update += browser_win->changed;
1607 break;
1608 case FILTERS_VIEW:
1609 needs_view_update += filters_win->changed;
1610 break;
1613 /* total time changed? */
1614 if (play_library) {
1615 needs_status_update += lib_editable.win->changed;
1616 } else {
1617 needs_status_update += pl_editable.win->changed;
1620 editable_unlock();
1621 player_info_unlock();
1623 if (needs_spawn)
1624 spawn_status_program();
1626 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1627 curs_set(0);
1629 if (needs_view_update)
1630 do_update_view(0);
1631 if (needs_title_update)
1632 do_update_titleline();
1633 if (needs_status_update)
1634 do_update_statusline();
1635 if (needs_command_update)
1636 do_update_commandline();
1637 post_update();
1641 static void handle_ch(uchar ch)
1643 clear_error();
1644 if (input_mode == NORMAL_MODE) {
1645 normal_mode_ch(ch);
1646 } else if (input_mode == COMMAND_MODE) {
1647 command_mode_ch(ch);
1648 update_commandline();
1649 } else if (input_mode == SEARCH_MODE) {
1650 search_mode_ch(ch);
1651 update_commandline();
1655 static void handle_key(int key)
1657 clear_error();
1658 if (input_mode == NORMAL_MODE) {
1659 normal_mode_key(key);
1660 } else if (input_mode == COMMAND_MODE) {
1661 command_mode_key(key);
1662 update_commandline();
1663 } else if (input_mode == SEARCH_MODE) {
1664 search_mode_key(key);
1665 update_commandline();
1669 static void u_getch(void)
1671 int key;
1672 int bit = 7;
1673 int mask = (1 << 7);
1674 uchar u, ch;
1676 key = getch();
1677 if (key == ERR || key == 0)
1678 return;
1680 if (key > 255) {
1681 handle_key(key);
1682 return;
1685 ch = (unsigned char)key;
1686 while (bit > 0 && ch & mask) {
1687 mask >>= 1;
1688 bit--;
1690 if (bit == 7) {
1691 /* ascii */
1692 u = ch;
1693 } else {
1694 int count;
1696 u = ch & ((1 << bit) - 1);
1697 count = 6 - bit;
1698 while (count) {
1699 key = getch();
1700 if (key == ERR || key == 0)
1701 return;
1703 ch = (unsigned char)key;
1704 u = (u << 6) | (ch & 63);
1705 count--;
1708 handle_ch(u);
1711 static void main_loop(void)
1713 int rc, fd_high;
1715 fd_high = remote_socket;
1716 while (running) {
1717 fd_set set;
1718 struct timeval tv;
1720 update();
1722 FD_ZERO(&set);
1723 FD_SET(0, &set);
1724 FD_SET(remote_socket, &set);
1726 /* Timeout must be so small that screen updates seem instant.
1727 * Only affects changes done in other threads (worker, player).
1729 * Too small timeout makes window updates too fast (wastes CPU).
1731 * Too large timeout makes status line (position) updates too slow.
1732 * The timeout is accuracy of player position.
1734 tv.tv_sec = 0;
1735 tv.tv_usec = 100e3;
1736 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1737 if (rc <= 0) {
1738 if (ctrl_c_pressed) {
1739 handle_ch(0x03);
1740 ctrl_c_pressed = 0;
1742 continue;
1745 if (FD_ISSET(remote_socket, &set)) {
1746 /* no error msgs for cmus-remote */
1747 display_errors = 0;
1749 remote_server_serve();
1751 if (FD_ISSET(0, &set)) {
1752 /* diplay errors for interactive commands */
1753 display_errors = 1;
1755 if (using_utf8) {
1756 u_getch();
1757 } else {
1758 int key = getch();
1760 if (key != ERR && key != 0) {
1761 if (key > 255) {
1762 handle_key(key);
1763 } else {
1764 uchar ch = key;
1766 if (ch > 0x7f)
1767 ch |= U_INVALID_MASK;
1768 handle_ch(ch);
1776 static int get_next(char **filename)
1778 struct track_info *info;
1780 editable_lock();
1781 info = play_queue_remove();
1782 if (info == NULL) {
1783 if (play_library) {
1784 info = lib_set_next();
1785 } else {
1786 info = pl_set_next();
1789 editable_unlock();
1791 if (info == NULL)
1792 return -1;
1794 *filename = xstrdup(info->filename);
1795 track_info_unref(info);
1796 return 0;
1799 static const struct player_callbacks player_callbacks = {
1800 .get_next = get_next
1803 static void init_curses(void)
1805 struct sigaction act;
1806 char *ptr, *term;
1808 sigemptyset(&act.sa_mask);
1809 act.sa_flags = 0;
1810 act.sa_handler = sig_int;
1811 sigaction(SIGINT, &act, NULL);
1813 sigemptyset(&act.sa_mask);
1814 act.sa_flags = 0;
1815 act.sa_handler = sig_winch;
1816 sigaction(SIGWINCH, &act, NULL);
1818 initscr();
1820 /* turn off kb buffering */
1821 cbreak();
1823 keypad(stdscr, TRUE);
1825 /* wait max 5 * 0.1 s if there are no keys available
1826 * doesn't really matter because we use select()
1828 halfdelay(5);
1830 noecho();
1831 if (has_colors()) {
1832 start_color();
1833 use_default_colors();
1835 d_print("Number of supported colors: %d\n", COLORS);
1836 ui_initialized = 1;
1838 /* this was disabled while initializing because it needs to be
1839 * called only once after all colors have been set
1841 update_colors();
1843 ptr = print_buffer;
1844 t_ts = tgetstr("ts", &ptr);
1845 t_fs = tgetstr("fs", &ptr);
1846 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
1848 if (!t_fs)
1849 t_ts = NULL;
1851 if (!t_ts && (term = getenv("TERM"))) {
1853 * Eterm: Eterm
1854 * aterm: rxvt
1855 * mlterm: xterm
1856 * terminal (xfce): xterm
1857 * urxvt: rxvt-unicode
1858 * xterm: xterm, xterm-{,16,88,256}color
1860 if (!strcmp(term, "screen")) {
1861 t_ts = "\033k";
1862 t_fs = "\033\\";
1863 } else if (!strncmp(term, "xterm", 5) ||
1864 !strncmp(term, "rxvt", 4) ||
1865 !strcmp(term, "Eterm")) {
1866 /* \033]1; change icon
1867 * \033]2; change title
1868 * \033]0; change both
1870 t_ts = "\033]0;";
1871 t_fs = "\007";
1876 static void init_all(void)
1878 remote_socket = remote_server_init(server_address);
1880 /* does not select output plugin */
1881 player_init(&player_callbacks);
1883 /* plugins have been loaded so we know what plugin options are available */
1884 options_add();
1886 lib_init();
1887 searchable = tree_searchable;
1888 pl_init();
1889 cmus_init();
1890 browser_init();
1891 filters_init();
1892 cmdline_init();
1893 commands_init();
1894 search_mode_init();
1896 /* almost everything must be initialized now */
1897 options_load();
1899 /* finally we can set the output plugin */
1900 player_set_op(output_plugin);
1902 player_get_volume(&player_info.vol_left, &player_info.vol_right);
1904 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
1905 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
1906 pl_filename = xstrdup(pl_autosave_filename);
1907 lib_filename = xstrdup(lib_autosave_filename);
1909 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
1910 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
1912 if (error_count) {
1913 char buf[16];
1915 warn("Press <enter> to continue.");
1916 fgets(buf, sizeof(buf), stdin);
1919 init_curses();
1922 static void exit_all(void)
1924 endwin();
1926 options_exit();
1928 remote_server_exit();
1929 cmus_exit();
1930 cmus_save(lib_for_each, lib_autosave_filename);
1931 cmus_save(pl_for_each, pl_autosave_filename);
1933 player_exit();
1934 commands_exit();
1935 search_mode_exit();
1936 filters_exit();
1937 browser_exit();
1940 enum {
1941 FLAG_LISTEN,
1942 FLAG_PLUGINS,
1943 FLAG_HELP,
1944 FLAG_VERSION,
1945 NR_FLAGS
1948 static struct option options[NR_FLAGS + 1] = {
1949 { 0, "listen", 1 },
1950 { 0, "plugins", 0 },
1951 { 0, "help", 0 },
1952 { 0, "version", 0 },
1953 { 0, NULL, 0 }
1956 static const char *usage =
1957 "Usage: %s [OPTION]...\n"
1958 "Curses based music player.\n"
1959 "\n"
1960 " --listen ADDR listen ADDR (unix socket) instead of /tmp/cmus-$USER\n"
1961 " --plugins list available plugins and exit\n"
1962 " --help display this help and exit\n"
1963 " --version " VERSION "\n"
1964 "\n"
1965 "Use cmus-remote to control cmus from command line.\n"
1966 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
1968 int main(int argc, char *argv[])
1970 int list_plugins = 0;
1972 program_name = argv[0];
1973 argv++;
1974 while (1) {
1975 int idx;
1976 char *arg;
1978 idx = get_option(&argv, options, &arg);
1979 if (idx < 0)
1980 break;
1982 switch (idx) {
1983 case FLAG_HELP:
1984 printf(usage, program_name);
1985 return 0;
1986 case FLAG_VERSION:
1987 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
1988 return 0;
1989 case FLAG_PLUGINS:
1990 list_plugins = 1;
1991 break;
1992 case FLAG_LISTEN:
1993 server_address = xstrdup(arg);
1994 break;
1998 setlocale(LC_CTYPE, "");
1999 #ifdef CODESET
2000 charset = nl_langinfo(CODESET);
2001 #else
2002 charset = "ISO-8859-1";
2003 #endif
2004 if (strcmp(charset, "UTF-8") == 0) {
2005 using_utf8 = 1;
2006 } else {
2007 using_utf8 = 0;
2009 misc_init();
2010 if (server_address == NULL)
2011 server_address = xstrjoin(home_dir, "/.cmus/socket");
2012 debug_init();
2013 d_print("charset = '%s'\n", charset);
2015 player_load_plugins();
2016 if (list_plugins) {
2017 player_dump_plugins();
2018 return 0;
2020 init_all();
2021 main_loop();
2022 exit_all();
2023 return 0;