seek: Ceil position to duration - 5s
[cmus.git] / ui_curses.c
blobe847b520ee4b7ba75fa8f0fcfd454e0b9c5b5a3e
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "ui_curses.h"
21 #include "cmdline.h"
22 #include "search_mode.h"
23 #include "command_mode.h"
24 #include "options.h"
25 #include "play_queue.h"
26 #include "browser.h"
27 #include "filters.h"
28 #include "cmus.h"
29 #include "player.h"
30 #include "utils.h"
31 #include "lib.h"
32 #include "pl.h"
33 #include "xmalloc.h"
34 #include "xstrjoin.h"
35 #include "window.h"
36 #include "format_print.h"
37 #include "misc.h"
38 #include "prog.h"
39 #include "uchar.h"
40 #include "spawn.h"
41 #include "server.h"
42 #include "keys.h"
43 #include "debug.h"
44 #include "help.h"
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <errno.h>
50 #include <sys/ioctl.h>
51 #include <ctype.h>
52 #include <dirent.h>
53 #include <locale.h>
54 #include <langinfo.h>
55 #include <iconv.h>
56 #include <signal.h>
57 #include <stdarg.h>
59 #if defined(__sun__) || defined(__CYGWIN__)
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;
77 char *lib_filename = NULL;
78 char *pl_filename = NULL;
80 /* ------------------------------------------------------------------------- */
82 static int running = 1;
83 static char *lib_autosave_filename;
84 static char *pl_autosave_filename;
86 /* shown error message and time stamp
87 * error is cleared if it is older than 3s and key was pressed
89 static char error_buf[512];
90 static time_t error_time = 0;
91 /* info messages are displayed in different color */
92 static int msg_is_error;
93 static int error_count = 0;
95 static char *server_address = NULL;
97 static char *charset = NULL;
98 static char print_buffer[512];
100 /* destination buffer for utf8_encode and utf8_decode */
101 static char conv_buffer[512];
103 #define print_buffer_size (sizeof(print_buffer) - 1)
104 static int using_utf8;
106 static const char *t_ts;
107 static const char *t_fs;
109 static int tree_win_x = 0;
110 static int tree_win_y = 0;
111 static int tree_win_w = 0;
113 static int track_win_x = 0;
114 static int track_win_y = 0;
115 static int track_win_w = 0;
117 static int cursor_x;
118 static int cursor_y;
120 enum {
121 CURSED_WIN,
122 CURSED_WIN_CUR,
123 CURSED_WIN_SEL,
124 CURSED_WIN_SEL_CUR,
126 CURSED_WIN_ACTIVE,
127 CURSED_WIN_ACTIVE_CUR,
128 CURSED_WIN_ACTIVE_SEL,
129 CURSED_WIN_ACTIVE_SEL_CUR,
131 CURSED_SEPARATOR,
132 CURSED_WIN_TITLE,
133 CURSED_COMMANDLINE,
134 CURSED_STATUSLINE,
136 CURSED_TITLELINE,
137 CURSED_DIR,
138 CURSED_ERROR,
139 CURSED_INFO,
141 NR_CURSED
144 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
145 COLOR_WIN_BG,
146 COLOR_WIN_BG,
147 COLOR_WIN_INACTIVE_SEL_BG,
148 COLOR_WIN_INACTIVE_CUR_SEL_BG,
150 COLOR_WIN_BG,
151 COLOR_WIN_BG,
152 COLOR_WIN_SEL_BG,
153 COLOR_WIN_CUR_SEL_BG,
155 COLOR_WIN_BG,
156 COLOR_WIN_TITLE_BG,
157 COLOR_CMDLINE_BG,
158 COLOR_STATUSLINE_BG,
160 COLOR_TITLELINE_BG,
161 COLOR_WIN_BG,
162 COLOR_CMDLINE_BG,
163 COLOR_CMDLINE_BG
166 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
167 COLOR_WIN_FG,
168 COLOR_WIN_CUR,
169 COLOR_WIN_INACTIVE_SEL_FG,
170 COLOR_WIN_INACTIVE_CUR_SEL_FG,
172 COLOR_WIN_FG,
173 COLOR_WIN_CUR,
174 COLOR_WIN_SEL_FG,
175 COLOR_WIN_CUR_SEL_FG,
177 COLOR_SEPARATOR,
178 COLOR_WIN_TITLE_FG,
179 COLOR_CMDLINE_FG,
180 COLOR_STATUSLINE_FG,
182 COLOR_TITLELINE_FG,
183 COLOR_WIN_DIR,
184 COLOR_ERROR,
185 COLOR_INFO
188 /* index is CURSED_*, value is fucking color pair */
189 static int pairs[NR_CURSED];
191 enum {
192 TF_ARTIST,
193 TF_ALBUM,
194 TF_DISC,
195 TF_TRACK,
196 TF_TITLE,
197 TF_YEAR,
198 TF_GENRE,
199 TF_DURATION,
200 TF_PATHFILE,
201 TF_FILE,
202 NR_TFS
205 static struct format_option track_fopts[NR_TFS + 1] = {
206 DEF_FO_STR('a'),
207 DEF_FO_STR('l'),
208 DEF_FO_INT('D'),
209 DEF_FO_INT('n'),
210 DEF_FO_STR('t'),
211 DEF_FO_STR('y'),
212 DEF_FO_STR('g'),
213 DEF_FO_TIME('d'),
214 DEF_FO_STR('f'),
215 DEF_FO_STR('F'),
216 DEF_FO_END
219 enum {
220 SF_STATUS,
221 SF_POSITION,
222 SF_DURATION,
223 SF_TOTAL,
224 SF_VOLUME,
225 SF_LVOLUME,
226 SF_RVOLUME,
227 SF_BUFFER,
228 SF_REPEAT,
229 SF_CONTINUE,
230 SF_SHUFFLE,
231 SF_PLAYLISTMODE,
232 NR_SFS
235 static struct format_option status_fopts[NR_SFS + 1] = {
236 DEF_FO_STR('s'),
237 DEF_FO_TIME('p'),
238 DEF_FO_TIME('d'),
239 DEF_FO_TIME('t'),
240 DEF_FO_INT('v'),
241 DEF_FO_INT('l'),
242 DEF_FO_INT('r'),
243 DEF_FO_INT('b'),
244 DEF_FO_STR('R'),
245 DEF_FO_STR('C'),
246 DEF_FO_STR('S'),
247 DEF_FO_STR('L'),
248 DEF_FO_END
251 static void utf8_encode(const char *buffer)
253 static iconv_t cd = (iconv_t)-1;
254 size_t is, os;
255 const char *i;
256 char *o;
257 int rc;
259 if (cd == (iconv_t)-1) {
260 d_print("iconv_open(UTF-8, %s)\n", charset);
261 cd = iconv_open("UTF-8", charset);
262 if (cd == (iconv_t)-1) {
263 d_print("iconv_open failed: %s\n", strerror(errno));
264 return;
267 i = buffer;
268 o = conv_buffer;
269 is = strlen(i);
270 os = sizeof(conv_buffer) - 1;
271 rc = iconv(cd, (void *)&i, &is, &o, &os);
272 *o = 0;
273 if (rc == -1) {
274 d_print("iconv failed: %s\n", strerror(errno));
275 return;
279 static void utf8_decode(const char *buffer)
281 static iconv_t cd = (iconv_t)-1;
282 size_t is, os;
283 const char *i;
284 char *o;
285 int rc;
287 if (cd == (iconv_t)-1) {
288 d_print("iconv_open(%s, UTF-8)\n", charset);
289 cd = iconv_open(charset, "UTF-8");
290 if (cd == (iconv_t)-1) {
291 d_print("iconv_open failed: %s\n", strerror(errno));
292 return;
295 i = buffer;
296 o = conv_buffer;
297 is = strlen(i);
298 os = sizeof(conv_buffer) - 1;
299 rc = iconv(cd, (void *)&i, &is, &o, &os);
300 *o = 0;
301 if (rc == -1) {
302 d_print("iconv failed: %s\n", strerror(errno));
303 return;
307 /* screen updates {{{ */
309 static void dump_print_buffer(int row, int col)
311 if (using_utf8) {
312 mvaddstr(row, col, print_buffer);
313 } else {
314 utf8_decode(print_buffer);
315 mvaddstr(row, col, conv_buffer);
319 /* print @str into @buf
321 * if @str is shorter than @width pad with spaces
322 * if @str is wider than @width truncate and add "..."
324 static int format_str(char *buf, const char *str, int width)
326 int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;
328 while (1) {
329 uchar u;
330 int w;
332 u_get_char(str, &s, &u);
333 if (u == 0) {
334 memset(buf + d, ' ', width);
335 d += width;
336 break;
339 w = u_char_width(u);
340 if (width == 3)
341 ellipsis_pos = d;
342 if (width == 4 && w == 2) {
343 /* can't cut double-width char */
344 ellipsis_pos = d + 1;
345 cut_double_width = 1;
348 width -= w;
349 if (width < 0) {
350 /* does not fit */
351 d = ellipsis_pos;
352 if (cut_double_width) {
353 /* first half of the double-width char */
354 buf[d - 1] = ' ';
356 buf[d++] = '.';
357 buf[d++] = '.';
358 buf[d++] = '.';
359 break;
361 u_set_char(buf, &d, u);
363 return d;
366 static void sprint(int row, int col, const char *str, int width)
368 int pos = 0;
370 print_buffer[pos++] = ' ';
371 pos += format_str(print_buffer + pos, str, width - 2);
372 print_buffer[pos++] = ' ';
373 print_buffer[pos] = 0;
374 dump_print_buffer(row, col);
377 static void sprint_ascii(int row, int col, const char *str, int len)
379 int l;
381 l = strlen(str);
382 len -= 2;
384 print_buffer[0] = ' ';
385 if (l > len) {
386 memcpy(print_buffer + 1, str, len - 3);
387 print_buffer[len - 2] = '.';
388 print_buffer[len - 1] = '.';
389 print_buffer[len - 0] = '.';
390 } else {
391 memcpy(print_buffer + 1, str, l);
392 memset(print_buffer + 1 + l, ' ', len - l);
394 print_buffer[len + 1] = ' ';
395 print_buffer[len + 2] = 0;
396 mvaddstr(row, col, print_buffer);
399 static void print_tree(struct window *win, int row, struct iter *iter)
401 const char *str;
402 struct artist *artist;
403 struct album *album;
404 struct iter sel;
405 int current, selected, active, pos;
407 artist = iter_to_artist(iter);
408 album = iter_to_album(iter);
409 current = 0;
410 if (lib_cur_track) {
411 if (album) {
412 current = CUR_ALBUM == album;
413 } else {
414 current = CUR_ARTIST == artist;
417 window_get_sel(win, &sel);
418 selected = iters_equal(iter, &sel);
419 active = lib_cur_win == lib_tree_win;
420 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
422 if (active && selected) {
423 cursor_x = 0;
424 cursor_y = 1 + row;
427 pos = 0;
428 print_buffer[pos++] = ' ';
429 str = artist->name;
430 if (album) {
431 print_buffer[pos++] = ' ';
432 print_buffer[pos++] = ' ';
433 str = album->name;
435 pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
436 print_buffer[pos++] = ' ';
437 print_buffer[pos++] = 0;
438 dump_print_buffer(tree_win_y + row + 1, tree_win_x);
441 static inline void fopt_set_str(struct format_option *fopt, const char *str)
443 BUG_ON(fopt->type != FO_STR);
444 if (str) {
445 fopt->fo_str = str;
446 fopt->empty = 0;
447 } else {
448 fopt->empty = 1;
452 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
454 BUG_ON(fopt->type != FO_INT);
455 fopt->fo_int = value;
456 fopt->empty = empty;
459 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
461 BUG_ON(fopt->type != FO_TIME);
462 fopt->fo_time = value;
463 fopt->empty = empty;
466 static void fill_track_fopts_track_info(struct track_info *info)
468 char *filename;
469 int num, disc;
471 if (using_utf8) {
472 filename = info->filename;
473 } else {
474 utf8_encode(info->filename);
475 filename = conv_buffer;
477 disc = comments_get_int(info->comments, "discnumber");
478 num = comments_get_int(info->comments, "tracknumber");
480 fopt_set_str(&track_fopts[TF_ARTIST], comments_get_val(info->comments, "artist"));
481 fopt_set_str(&track_fopts[TF_ALBUM], comments_get_val(info->comments, "album"));
482 fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
483 fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
484 fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(info->comments, "title"));
485 fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(info->comments, "date"));
486 fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(info->comments, "genre"));
487 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
488 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
489 if (is_url(info->filename)) {
490 fopt_set_str(&track_fopts[TF_FILE], filename);
491 } else {
492 const char *f;
494 f = strrchr(filename, '/');
495 if (f) {
496 fopt_set_str(&track_fopts[TF_FILE], f + 1);
497 } else {
498 fopt_set_str(&track_fopts[TF_FILE], filename);
503 static void print_track(struct window *win, int row, struct iter *iter)
505 struct tree_track *track;
506 struct iter sel;
507 int current, selected, active;
509 track = iter_to_tree_track(iter);
510 current = lib_cur_track == track;
511 window_get_sel(win, &sel);
512 selected = iters_equal(iter, &sel);
513 active = lib_cur_win == lib_track_win;
514 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
516 if (active && selected) {
517 cursor_x = track_win_x;
518 cursor_y = 1 + row;
521 fill_track_fopts_track_info(tree_track_info(track));
523 if (track_info_has_tag(tree_track_info(track))) {
524 format_print(print_buffer, track_win_w, track_win_format, track_fopts);
525 } else {
526 format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
528 dump_print_buffer(track_win_y + row + 1, track_win_x);
531 /* used by print_editable only */
532 static struct simple_track *current_track;
534 static void print_editable(struct window *win, int row, struct iter *iter)
536 struct simple_track *track;
537 struct iter sel;
538 int current, selected, active;
540 track = iter_to_simple_track(iter);
541 current = current_track == track;
542 window_get_sel(win, &sel);
543 selected = iters_equal(iter, &sel);
545 if (selected) {
546 cursor_x = 0;
547 cursor_y = 1 + row;
550 active = 1;
551 if (!selected && track->marked) {
552 selected = 1;
553 active = 0;
556 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
558 fill_track_fopts_track_info(track->info);
560 if (track_info_has_tag(track->info)) {
561 format_print(print_buffer, COLS, list_win_format, track_fopts);
562 } else {
563 format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
565 dump_print_buffer(row + 1, 0);
568 static void print_browser(struct window *win, int row, struct iter *iter)
570 struct browser_entry *e;
571 struct iter sel;
572 int selected;
574 e = iter_to_browser_entry(iter);
575 window_get_sel(win, &sel);
576 selected = iters_equal(iter, &sel);
577 if (selected) {
578 int active = 1;
579 int current = 0;
581 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
582 } else {
583 if (e->type == BROWSER_ENTRY_DIR) {
584 bkgdset(pairs[CURSED_DIR]);
585 } else {
586 bkgdset(pairs[CURSED_WIN]);
590 if (selected) {
591 cursor_x = 0;
592 cursor_y = 1 + row;
595 /* file name encoding == terminal encoding. no need to convert */
596 if (using_utf8) {
597 sprint(row + 1, 0, e->name, COLS);
598 } else {
599 sprint_ascii(row + 1, 0, e->name, COLS);
603 static void print_filter(struct window *win, int row, struct iter *iter)
605 char buf[256];
606 struct filter_entry *e = iter_to_filter_entry(iter);
607 struct iter sel;
608 /* window active? */
609 int active = 1;
610 /* row selected? */
611 int selected;
612 /* is the filter currently active? */
613 int current = !!e->act_stat;
614 const char stat_chars[3] = " *!";
615 int ch1, ch2, ch3, pos;
617 window_get_sel(win, &sel);
618 selected = iters_equal(iter, &sel);
619 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
621 if (selected) {
622 cursor_x = 0;
623 cursor_y = 1 + row;
626 ch1 = ' ';
627 ch3 = ' ';
628 if (e->sel_stat != e->act_stat) {
629 ch1 = '[';
630 ch3 = ']';
632 ch2 = stat_chars[e->sel_stat];
633 snprintf(buf, sizeof(buf), "%c%c%c%-15s %s", ch1, ch2, ch3, e->name, e->filter);
634 pos = format_str(print_buffer, buf, COLS - 1);
635 print_buffer[pos++] = ' ';
636 print_buffer[pos] = 0;
637 dump_print_buffer(row + 1, 0);
640 static void print_help(struct window *win, int row, struct iter *iter)
642 struct iter sel;
643 int selected;
644 int pos;
645 int active = 1;
646 char buf[512];
647 const struct help_entry *e = iter_to_help_entry(iter);
648 const struct cmus_opt *opt;
650 window_get_sel(win, &sel);
651 selected = iters_equal(iter, &sel);
652 bkgdset(pairs[(active << 2) | (selected << 1)]);
654 if (selected) {
655 cursor_x = 0;
656 cursor_y = 1 + row;
659 switch (e->type) {
660 case HE_TEXT:
661 snprintf(buf, sizeof(buf), " %s", e->text);
662 break;
663 case HE_BOUND:
664 snprintf(buf, sizeof(buf), " %-8s %-14s %s",
665 key_context_names[e->binding->ctx],
666 e->binding->key->name,
667 e->binding->cmd);
668 break;
669 case HE_UNBOUND:
670 snprintf(buf, sizeof(buf), " %s", e->command->name);
671 break;
672 case HE_OPTION:
673 opt = e->option;
674 snprintf(buf, sizeof(buf), " %-29s ", opt->name);
675 opt->get(opt->id, buf + strlen(buf));
676 break;
678 pos = format_str(print_buffer, buf, COLS - 1);
679 print_buffer[pos++] = ' ';
680 print_buffer[pos] = 0;
681 dump_print_buffer(row + 1, 0);
684 static void update_window(struct window *win, int x, int y, int w, const char *title,
685 void (*print)(struct window *, int, struct iter *))
687 struct iter iter;
688 int nr_rows;
689 int c, i;
691 win->changed = 0;
693 bkgdset(pairs[CURSED_WIN_TITLE]);
694 c = snprintf(print_buffer, w + 1, " %s", title);
695 if (c > w)
696 c = w;
697 memset(print_buffer + c, ' ', w - c + 1);
698 print_buffer[w] = 0;
699 dump_print_buffer(y, x);
700 nr_rows = window_get_nr_rows(win);
701 i = 0;
702 if (window_get_top(win, &iter)) {
703 while (i < nr_rows) {
704 print(win, i, &iter);
705 i++;
706 if (!window_get_next(win, &iter))
707 break;
711 bkgdset(pairs[0]);
712 memset(print_buffer, ' ', w);
713 print_buffer[w] = 0;
714 while (i < nr_rows) {
715 dump_print_buffer(y + i + 1, x);
716 i++;
720 static void update_tree_window(void)
722 update_window(lib_tree_win, tree_win_x, tree_win_y,
723 tree_win_w, "Artist / Album", print_tree);
726 static void update_track_window(void)
728 char title[512];
730 /* it doesn't matter what format options we use because the format
731 * string does not contain any format charaters */
732 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
733 update_window(lib_track_win, track_win_x, track_win_y,
734 track_win_w, title, print_track);
737 static const char *pretty(const char *path)
739 static int home_len = -1;
740 static char buf[256];
742 if (home_len == -1)
743 home_len = strlen(home_dir);
745 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
746 return path;
748 buf[0] = '~';
749 strcpy(buf + 1, path + home_len);
750 return buf;
753 static const char * const sorted_names[2] = { "", "sorted by " };
755 static void update_editable_window(struct editable *e, const char *title, const char *filename)
757 char buf[512];
758 int pos;
760 if (filename) {
761 if (using_utf8) {
762 /* already UTF-8 */
763 } else {
764 utf8_encode(filename);
765 filename = conv_buffer;
767 snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
768 pretty(filename), e->nr_tracks);
769 } else {
770 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
773 if (e->nr_marked) {
774 pos = strlen(buf);
775 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
777 pos = strlen(buf);
778 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
779 sorted_names[e->sort_str[0] != 0], e->sort_str);
781 update_window(e->win, 0, 0, COLS, buf, &print_editable);
784 static void update_sorted_window(void)
786 current_track = (struct simple_track *)lib_cur_track;
787 update_editable_window(&lib_editable, "Library", lib_filename);
790 static void update_pl_window(void)
792 current_track = pl_cur_track;
793 update_editable_window(&pl_editable, "Playlist", pl_filename);
796 static void update_play_queue_window(void)
798 current_track = NULL;
799 update_editable_window(&pq_editable, "Play Queue", NULL);
802 static void update_browser_window(void)
804 char title[512];
805 char *dirname;
807 if (using_utf8) {
808 /* already UTF-8 */
809 dirname = browser_dir;
810 } else {
811 utf8_encode(browser_dir);
812 dirname = conv_buffer;
814 snprintf(title, sizeof(title), "Browser - %s", dirname);
815 update_window(browser_win, 0, 0, COLS, title, print_browser);
818 static void update_filters_window(void)
820 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
823 static void update_help_window(void)
825 update_window(help_win, 0, 0, COLS, "Settings", print_help);
828 static void draw_separator(void)
830 int row;
832 bkgdset(pairs[CURSED_WIN_TITLE]);
833 mvaddch(0, tree_win_w, ' ');
834 bkgdset(pairs[CURSED_SEPARATOR]);
835 for (row = 1; row < LINES - 3; row++)
836 mvaddch(row, tree_win_w, ACS_VLINE);
839 static void do_update_view(int full)
841 cursor_x = -1;
842 cursor_y = -1;
844 switch (cur_view) {
845 case TREE_VIEW:
846 editable_lock();
847 if (full || lib_tree_win->changed)
848 update_tree_window();
849 if (full || lib_track_win->changed)
850 update_track_window();
851 editable_unlock();
852 draw_separator();
853 break;
854 case SORTED_VIEW:
855 editable_lock();
856 update_sorted_window();
857 editable_unlock();
858 break;
859 case PLAYLIST_VIEW:
860 editable_lock();
861 update_pl_window();
862 editable_unlock();
863 break;
864 case QUEUE_VIEW:
865 editable_lock();
866 update_play_queue_window();
867 editable_unlock();
868 break;
869 case BROWSER_VIEW:
870 update_browser_window();
871 break;
872 case FILTERS_VIEW:
873 update_filters_window();
874 break;
875 case HELP_VIEW:
876 update_help_window();
877 break;
881 static void do_update_statusline(void)
883 static const char *status_strs[] = { ".", ">", "|" };
884 static const char *cont_strs[] = { " ", "C" };
885 static const char *repeat_strs[] = { " ", "R" };
886 static const char *shuffle_strs[] = { " ", "S" };
887 int buffer_fill, vol, vol_left, vol_right;
888 int duration = -1;
889 char *msg;
890 char format[80];
892 editable_lock();
893 fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
894 pl_editable.total_time, 0);
895 editable_unlock();
897 fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
898 fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
899 fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
901 player_info_lock();
903 if (player_info.ti)
904 duration = player_info.ti->duration;
906 if (volume_max == 0) {
907 vol_left = vol_right = vol = -1;
908 } else {
909 vol_left = scale_to_percentage(player_info.vol_left, volume_max);
910 vol_right = scale_to_percentage(player_info.vol_right, volume_max);
911 vol = (vol_left + vol_right + 1) / 2;
913 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
915 fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
917 if (show_remaining_time && duration != -1) {
918 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
919 } else {
920 fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
923 fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
924 fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
925 fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
926 fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
927 fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
928 fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
930 strcpy(format, " %s %p ");
931 if (duration != -1)
932 strcat(format, "/ %d ");
933 strcat(format, "- %t ");
934 if (volume_max != 0) {
935 if (player_info.vol_left != player_info.vol_right) {
936 strcat(format, "vol: %l,%r ");
937 } else {
938 strcat(format, "vol: %v ");
941 if (player_info.ti && is_url(player_info.ti->filename))
942 strcat(format, "buf: %b ");
943 strcat(format, "%=");
944 if (play_library) {
945 /* artist/album modes work only in lib */
946 if (shuffle) {
947 /* shuffle overrides sorted mode */
948 strcat(format, "%L from library");
949 } else if (play_sorted) {
950 strcat(format, "%L from sorted library");
951 } else {
952 strcat(format, "%L from library");
954 } else {
955 strcat(format, "playlist");
957 strcat(format, " | %1C%1R%1S ");
958 format_print(print_buffer, COLS, format, status_fopts);
960 msg = player_info.error_msg;
961 player_info.error_msg = NULL;
963 player_info_unlock();
965 bkgdset(pairs[CURSED_STATUSLINE]);
966 dump_print_buffer(LINES - 2, 0);
968 if (msg) {
969 error_msg("%s", msg);
970 free(msg);
974 static void dump_buffer(const char *buffer)
976 if (using_utf8) {
977 addstr(buffer);
978 } else {
979 utf8_decode(buffer);
980 addstr(conv_buffer);
984 static void do_update_commandline(void)
986 char *str;
987 int w, idx;
988 char ch;
990 move(LINES - 1, 0);
991 if (error_buf[0]) {
992 if (msg_is_error) {
993 bkgdset(pairs[CURSED_ERROR]);
994 } else {
995 bkgdset(pairs[CURSED_INFO]);
997 addstr(error_buf);
998 clrtoeol();
999 return;
1001 bkgdset(pairs[CURSED_COMMANDLINE]);
1002 if (input_mode == NORMAL_MODE) {
1003 clrtoeol();
1004 return;
1007 str = cmdline.line;
1008 if (!using_utf8) {
1009 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1010 * characters are invalid UTF-8 so it really is in locale's
1011 * encoding.
1013 * This code should be safe because cmdline.bpos ==
1014 * cmdline.cpos as every non-ASCII character is counted as one
1015 * invalid UTF-8 byte.
1017 * NOTE: This has nothing to do with widths of printed
1018 * characters. I.e. even if there were control characters
1019 * (displayed as <xx>) there would be no problem because bpos
1020 * still equals to cpos, I think.
1022 utf8_encode(cmdline.line);
1023 str = conv_buffer;
1026 /* COMMAND_MODE or SEARCH_MODE */
1027 w = u_str_width(str);
1028 ch = ':';
1029 if (input_mode == SEARCH_MODE)
1030 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
1032 if (w <= COLS - 2) {
1033 addch(ch);
1034 idx = u_copy_chars(print_buffer, str, &w);
1035 print_buffer[idx] = 0;
1036 dump_buffer(print_buffer);
1037 clrtoeol();
1038 } else {
1039 /* keep cursor as far right as possible */
1040 int skip, width, cw;
1042 /* cursor pos (width, not chars. doesn't count the ':') */
1043 cw = u_str_nwidth(str, cmdline.cpos);
1045 skip = cw + 2 - COLS;
1046 if (skip > 0) {
1047 /* skip the ':' */
1048 skip--;
1050 /* skip rest (if any) */
1051 idx = u_skip_chars(str, &skip);
1053 width = COLS;
1054 idx = u_copy_chars(print_buffer, str + idx, &width);
1055 while (width < COLS) {
1056 /* cursor is at end of the buffer
1057 * print 1, 2 or 3 spaces
1059 * To clarify:
1061 * If the last _skipped_ character was double-width we may need
1062 * to print 2 spaces.
1064 * If the last _skipped_ character was invalid UTF-8 we may need
1065 * to print 3 spaces.
1067 print_buffer[idx++] = ' ';
1068 width++;
1070 print_buffer[idx] = 0;
1071 dump_buffer(print_buffer);
1072 } else {
1073 /* print ':' + COLS - 1 chars */
1074 addch(ch);
1075 width = COLS - 1;
1076 idx = u_copy_chars(print_buffer, str, &width);
1077 print_buffer[idx] = 0;
1078 dump_buffer(print_buffer);
1083 /* lock player_info! */
1084 static const char *get_stream_title(void)
1086 static char stream_title[255 * 16 + 1];
1087 char *ptr, *title;
1089 ptr = strstr(player_info.metadata, "StreamTitle='");
1090 if (ptr == NULL)
1091 return NULL;
1092 ptr += 13;
1093 title = ptr;
1094 while (*ptr) {
1095 if (*ptr == '\'' && *(ptr + 1) == ';') {
1096 memcpy(stream_title, title, ptr - title);
1097 stream_title[ptr - title] = 0;
1098 return stream_title;
1100 ptr++;
1102 return NULL;
1105 static void set_title(const char *title)
1107 if (!set_term_title)
1108 return;
1110 if (t_ts) {
1111 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1112 fflush(stdout);
1116 static void do_update_titleline(void)
1118 bkgdset(pairs[CURSED_TITLELINE]);
1119 player_info_lock();
1120 if (player_info.ti) {
1121 int i, use_alt_format = 0;
1122 char *wtitle;
1124 fill_track_fopts_track_info(player_info.ti);
1125 if (is_url(player_info.ti->filename)) {
1126 const char *title = get_stream_title();
1128 if (title == NULL)
1129 use_alt_format = 1;
1130 fopt_set_str(&track_fopts[TF_TITLE], title);
1131 } else {
1132 use_alt_format = !track_info_has_tag(player_info.ti);
1135 if (use_alt_format) {
1136 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1137 } else {
1138 format_print(print_buffer, COLS, current_format, track_fopts);
1140 dump_print_buffer(LINES - 3, 0);
1142 /* set window title */
1143 if (use_alt_format) {
1144 format_print(print_buffer, sizeof(print_buffer) - 1,
1145 window_title_alt_format, track_fopts);
1146 } else {
1147 format_print(print_buffer, sizeof(print_buffer) - 1,
1148 window_title_format, track_fopts);
1151 /* remove whitespace */
1152 i = strlen(print_buffer) - 1;
1153 while (i > 0 && print_buffer[i] == ' ')
1154 i--;
1155 print_buffer[i + 1] = 0;
1157 if (using_utf8) {
1158 wtitle = print_buffer;
1159 } else {
1160 utf8_decode(print_buffer);
1161 wtitle = conv_buffer;
1164 set_title(wtitle);
1165 } else {
1166 move(LINES - 3, 0);
1167 clrtoeol();
1169 set_title("cmus " VERSION);
1171 player_info_unlock();
1174 static int cmdline_cursor_column(void)
1176 char *str;
1177 int cw, skip, s;
1179 str = cmdline.line;
1180 if (!using_utf8) {
1181 /* see do_update_commandline */
1182 utf8_encode(cmdline.line);
1183 str = conv_buffer;
1186 /* width of the text in the buffer before cursor */
1187 cw = u_str_nwidth(str, cmdline.cpos);
1189 if (1 + cw < COLS) {
1190 /* whole line is visible */
1191 return 1 + cw;
1194 /* beginning of cmdline is not visible */
1196 /* check if the first visible char in cmdline would be halved
1197 * double-width character (or invalid byte <xx>) which is not possible.
1198 * we need to skip the whole character and move cursor to COLS - 2
1199 * column. */
1200 skip = cw + 2 - COLS;
1202 /* skip the ':' */
1203 skip--;
1205 /* skip rest */
1206 s = skip;
1207 u_skip_chars(str, &s);
1208 if (s > skip) {
1209 /* the last skipped char was double-width or <xx> */
1210 return COLS - 1 - (s - skip);
1212 return COLS - 1;
1215 static void post_update(void)
1217 /* refresh makes cursor visible at least for urxvt */
1218 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1219 move(LINES - 1, cmdline_cursor_column());
1220 refresh();
1221 curs_set(1);
1222 } else {
1223 if (cursor_x >= 0) {
1224 move(cursor_y, cursor_x);
1225 } else {
1226 move(LINES - 1, 0);
1228 refresh();
1229 curs_set(0);
1233 void update_titleline(void)
1235 curs_set(0);
1236 do_update_titleline();
1237 post_update();
1240 void update_full(void)
1242 if (!ui_initialized)
1243 return;
1245 curs_set(0);
1247 do_update_view(1);
1248 do_update_titleline();
1249 do_update_statusline();
1250 do_update_commandline();
1252 post_update();
1255 static void update_commandline(void)
1257 curs_set(0);
1258 do_update_commandline();
1259 post_update();
1262 void update_statusline(void)
1264 if (!ui_initialized)
1265 return;
1267 curs_set(0);
1268 do_update_statusline();
1269 post_update();
1272 void info_msg(const char *format, ...)
1274 va_list ap;
1276 va_start(ap, format);
1277 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1278 va_end(ap);
1280 msg_is_error = 0;
1282 update_commandline();
1285 void error_msg(const char *format, ...)
1287 va_list ap;
1289 strcpy(error_buf, "Error: ");
1290 va_start(ap, format);
1291 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1292 va_end(ap);
1294 d_print("%s\n", error_buf);
1296 msg_is_error = 1;
1297 error_count++;
1299 if (ui_initialized) {
1300 error_time = time(NULL);
1301 update_commandline();
1302 } else {
1303 warn("%s\n", error_buf);
1304 error_buf[0] = 0;
1308 int yes_no_query(const char *format, ...)
1310 char buffer[512];
1311 va_list ap;
1312 int ret = 0;
1314 va_start(ap, format);
1315 vsnprintf(buffer, sizeof(buffer), format, ap);
1316 va_end(ap);
1318 move(LINES - 1, 0);
1319 bkgdset(pairs[CURSED_INFO]);
1321 /* no need to convert buffer.
1322 * it is always encoded in the right charset (assuming filenames are
1323 * encoded in same charset as LC_CTYPE).
1326 addstr(buffer);
1327 clrtoeol();
1328 refresh();
1330 while (1) {
1331 int ch = getch();
1333 if (ch == ERR || ch == 0)
1334 continue;
1335 if (ch == 'y')
1336 ret = 1;
1337 break;
1339 update_commandline();
1340 return ret;
1343 void search_not_found(void)
1345 const char *what = "Track";
1347 if (search_restricted) {
1348 switch (cur_view) {
1349 case TREE_VIEW:
1350 what = "Artist/album";
1351 break;
1352 case SORTED_VIEW:
1353 case PLAYLIST_VIEW:
1354 case QUEUE_VIEW:
1355 what = "Title";
1356 break;
1357 case BROWSER_VIEW:
1358 what = "File/Directory";
1359 break;
1360 case FILTERS_VIEW:
1361 what = "Filter";
1362 break;
1363 case HELP_VIEW:
1364 what = "Binding/command/option";
1365 break;
1367 } else {
1368 switch (cur_view) {
1369 case TREE_VIEW:
1370 case SORTED_VIEW:
1371 case PLAYLIST_VIEW:
1372 case QUEUE_VIEW:
1373 what = "Track";
1374 break;
1375 case BROWSER_VIEW:
1376 what = "File/Directory";
1377 break;
1378 case FILTERS_VIEW:
1379 what = "Filter";
1380 break;
1381 case HELP_VIEW:
1382 what = "Binding/command/option";
1383 break;
1386 info_msg("%s not found: %s", what, search_str ? : "");
1389 void set_view(int view)
1391 if (view == cur_view)
1392 return;
1394 cur_view = view;
1395 switch (cur_view) {
1396 case TREE_VIEW:
1397 searchable = tree_searchable;
1398 break;
1399 case SORTED_VIEW:
1400 searchable = lib_editable.searchable;
1401 break;
1402 case PLAYLIST_VIEW:
1403 searchable = pl_editable.searchable;
1404 break;
1405 case QUEUE_VIEW:
1406 searchable = pq_editable.searchable;
1407 break;
1408 case BROWSER_VIEW:
1409 searchable = browser_searchable;
1410 break;
1411 case FILTERS_VIEW:
1412 searchable = filters_searchable;
1413 break;
1414 case HELP_VIEW:
1415 searchable = help_searchable;
1416 update_help_window();
1417 break;
1420 curs_set(0);
1421 do_update_view(1);
1422 post_update();
1425 void enter_command_mode(void)
1427 error_buf[0] = 0;
1428 error_time = 0;
1429 input_mode = COMMAND_MODE;
1430 update_commandline();
1433 void enter_search_mode(void)
1435 error_buf[0] = 0;
1436 error_time = 0;
1437 input_mode = SEARCH_MODE;
1438 search_direction = SEARCH_FORWARD;
1439 update_commandline();
1442 void enter_search_backward_mode(void)
1444 error_buf[0] = 0;
1445 error_time = 0;
1446 input_mode = SEARCH_MODE;
1447 search_direction = SEARCH_BACKWARD;
1448 update_commandline();
1451 void quit(void)
1453 running = 0;
1456 void update_colors(void)
1458 int i;
1460 if (!ui_initialized)
1461 return;
1463 for (i = 0; i < NR_CURSED; i++) {
1464 int bg = colors[cursed_to_bg_idx[i]];
1465 int fg = colors[cursed_to_fg_idx[i]];
1466 int pair = i + 1;
1468 if (fg >= 8 && fg <= 15) {
1469 /* fg colors 8..15 are special (0..7 + bold) */
1470 init_pair(pair, fg & 7, bg);
1471 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
1472 } else {
1473 init_pair(pair, fg, bg);
1474 pairs[i] = COLOR_PAIR(pair);
1479 static void clear_error(void)
1481 time_t t = time(NULL);
1483 /* prevent accidental clearing of error messages */
1484 if (t - error_time < 2)
1485 return;
1487 if (error_buf[0]) {
1488 error_time = 0;
1489 error_buf[0] = 0;
1490 update_commandline();
1494 /* screen updates }}} */
1496 static void spawn_status_program(void)
1498 static const char *status_strs[] = { "stopped", "playing", "paused" };
1499 const char *stream_title = NULL;
1500 char *argv[32];
1501 int i, status;
1503 if (status_display_program == NULL || status_display_program[0] == 0)
1504 return;
1506 player_info_lock();
1507 status = player_info.status;
1508 if (status == 1)
1509 stream_title = get_stream_title();
1511 i = 0;
1512 argv[i++] = xstrdup(status_display_program);
1514 argv[i++] = xstrdup("status");
1515 argv[i++] = xstrdup(status_strs[status]);
1516 if (player_info.ti) {
1517 static const char *keys[] = {
1518 "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
1520 int j;
1522 if (is_url(player_info.ti->filename)) {
1523 argv[i++] = xstrdup("url");
1524 argv[i++] = xstrdup(player_info.ti->filename);
1525 if (stream_title) {
1526 argv[i++] = xstrdup("title");
1527 argv[i++] = xstrdup(stream_title);
1529 } else {
1530 char buf[32];
1532 argv[i++] = xstrdup("file");
1533 argv[i++] = xstrdup(player_info.ti->filename);
1534 for (j = 0; keys[j]; j++) {
1535 const char *key = keys[j];
1536 const char *val;
1538 val = comments_get_val(player_info.ti->comments, key);
1539 if (val) {
1540 argv[i++] = xstrdup(key);
1541 argv[i++] = xstrdup(val);
1544 snprintf(buf, sizeof(buf), "%d", player_info.ti->duration);
1545 argv[i++] = xstrdup("duration");
1546 argv[i++] = xstrdup(buf);
1549 argv[i++] = NULL;
1550 player_info_unlock();
1552 if (spawn(argv, &status) == -1)
1553 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1554 for (i = 0; argv[i]; i++)
1555 free(argv[i]);
1558 static int ctrl_c_pressed = 0;
1560 static void sig_int(int sig)
1562 ctrl_c_pressed = 1;
1565 static int needs_to_resize = 1;
1567 static void sig_winch(int sig)
1569 needs_to_resize = 1;
1572 static int get_window_size(int *lines, int *columns)
1574 struct winsize ws;
1576 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1577 return -1;
1578 *columns = ws.ws_col;
1579 *lines = ws.ws_row;
1580 return 0;
1583 static void resize_tree_view(int w, int h)
1585 tree_win_w = w / 3;
1586 track_win_w = w - tree_win_w - 1;
1587 if (tree_win_w < 8)
1588 tree_win_w = 8;
1589 if (track_win_w < 8)
1590 track_win_w = 8;
1591 tree_win_x = 0;
1592 tree_win_y = 0;
1593 track_win_x = tree_win_w + 1;
1594 track_win_y = 0;
1596 h--;
1597 window_set_nr_rows(lib_tree_win, h);
1598 window_set_nr_rows(lib_track_win, h);
1601 static void update(void)
1603 int needs_view_update = 0;
1604 int needs_title_update = 0;
1605 int needs_status_update = 0;
1606 int needs_command_update = 0;
1607 int needs_spawn = 0;
1609 if (needs_to_resize) {
1610 int w, h;
1611 int columns, lines;
1613 if (get_window_size(&lines, &columns) == 0) {
1614 needs_to_resize = 0;
1615 resizeterm(lines, columns);
1616 w = COLS;
1617 h = LINES - 3;
1618 if (w < 16)
1619 w = 16;
1620 if (h < 8)
1621 h = 8;
1622 editable_lock();
1623 resize_tree_view(w, h);
1624 window_set_nr_rows(lib_editable.win, h - 1);
1625 window_set_nr_rows(pl_editable.win, h - 1);
1626 window_set_nr_rows(pq_editable.win, h - 1);
1627 window_set_nr_rows(filters_win, h - 1);
1628 window_set_nr_rows(help_win, h - 1);
1629 window_set_nr_rows(browser_win, h - 1);
1630 editable_unlock();
1631 needs_title_update = 1;
1632 needs_status_update = 1;
1633 needs_command_update = 1;
1637 player_info_lock();
1638 editable_lock();
1640 needs_spawn = player_info.status_changed || player_info.file_changed ||
1641 player_info.metadata_changed;
1643 if (player_info.file_changed) {
1644 player_info.file_changed = 0;
1645 needs_title_update = 1;
1646 needs_status_update = 1;
1648 if (player_info.metadata_changed) {
1649 player_info.metadata_changed = 0;
1650 needs_title_update = 1;
1652 if (player_info.position_changed || player_info.status_changed || player_info.vol_changed) {
1653 player_info.position_changed = 0;
1654 player_info.status_changed = 0;
1655 player_info.vol_changed = 0;
1657 needs_status_update = 1;
1659 switch (cur_view) {
1660 case TREE_VIEW:
1661 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
1662 break;
1663 case SORTED_VIEW:
1664 needs_view_update += lib_editable.win->changed;
1665 break;
1666 case PLAYLIST_VIEW:
1667 needs_view_update += pl_editable.win->changed;
1668 break;
1669 case QUEUE_VIEW:
1670 needs_view_update += pq_editable.win->changed;
1671 break;
1672 case BROWSER_VIEW:
1673 needs_view_update += browser_win->changed;
1674 break;
1675 case FILTERS_VIEW:
1676 needs_view_update += filters_win->changed;
1677 break;
1678 case HELP_VIEW:
1679 needs_view_update += help_win->changed;
1680 break;
1683 /* total time changed? */
1684 if (play_library) {
1685 needs_status_update += lib_editable.win->changed;
1686 lib_editable.win->changed = 0;
1687 } else {
1688 needs_status_update += pl_editable.win->changed;
1689 lib_editable.win->changed = 0;
1692 editable_unlock();
1693 player_info_unlock();
1695 if (needs_spawn)
1696 spawn_status_program();
1698 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
1699 curs_set(0);
1701 if (needs_view_update)
1702 do_update_view(0);
1703 if (needs_title_update)
1704 do_update_titleline();
1705 if (needs_status_update)
1706 do_update_statusline();
1707 if (needs_command_update)
1708 do_update_commandline();
1709 post_update();
1713 static void handle_ch(uchar ch)
1715 clear_error();
1716 if (input_mode == NORMAL_MODE) {
1717 normal_mode_ch(ch);
1718 } else if (input_mode == COMMAND_MODE) {
1719 command_mode_ch(ch);
1720 update_commandline();
1721 } else if (input_mode == SEARCH_MODE) {
1722 search_mode_ch(ch);
1723 update_commandline();
1727 static void handle_key(int key)
1729 clear_error();
1730 if (input_mode == NORMAL_MODE) {
1731 normal_mode_key(key);
1732 } else if (input_mode == COMMAND_MODE) {
1733 command_mode_key(key);
1734 update_commandline();
1735 } else if (input_mode == SEARCH_MODE) {
1736 search_mode_key(key);
1737 update_commandline();
1741 static void u_getch(void)
1743 int key;
1744 int bit = 7;
1745 int mask = (1 << 7);
1746 uchar u, ch;
1748 key = getch();
1749 if (key == ERR || key == 0)
1750 return;
1752 if (key > 255) {
1753 handle_key(key);
1754 return;
1757 ch = (unsigned char)key;
1758 while (bit > 0 && ch & mask) {
1759 mask >>= 1;
1760 bit--;
1762 if (bit == 7) {
1763 /* ascii */
1764 u = ch;
1765 } else {
1766 int count;
1768 u = ch & ((1 << bit) - 1);
1769 count = 6 - bit;
1770 while (count) {
1771 key = getch();
1772 if (key == ERR || key == 0)
1773 return;
1775 ch = (unsigned char)key;
1776 u = (u << 6) | (ch & 63);
1777 count--;
1780 handle_ch(u);
1783 static void main_loop(void)
1785 int rc, fd_high;
1787 fd_high = server_socket;
1788 while (running) {
1789 fd_set set;
1790 struct timeval tv;
1792 update();
1794 FD_ZERO(&set);
1795 FD_SET(0, &set);
1796 FD_SET(server_socket, &set);
1798 /* Timeout must be so small that screen updates seem instant.
1799 * Only affects changes done in other threads (worker, player).
1801 * Too small timeout makes window updates too fast (wastes CPU).
1803 * Too large timeout makes status line (position) updates too slow.
1804 * The timeout is accuracy of player position.
1806 tv.tv_sec = 0;
1807 tv.tv_usec = 100e3;
1808 rc = select(fd_high + 1, &set, NULL, NULL, &tv);
1809 if (rc <= 0) {
1810 if (ctrl_c_pressed) {
1811 handle_ch(0x03);
1812 ctrl_c_pressed = 0;
1814 continue;
1817 if (FD_ISSET(server_socket, &set))
1818 server_serve();
1819 if (FD_ISSET(0, &set)) {
1820 if (using_utf8) {
1821 u_getch();
1822 } else {
1823 int key = getch();
1825 if (key != ERR && key != 0) {
1826 if (key > 255) {
1827 handle_key(key);
1828 } else {
1829 uchar ch = key;
1831 if (ch > 0x7f)
1832 ch |= U_INVALID_MASK;
1833 handle_ch(ch);
1841 static int get_next(struct track_info **ti)
1843 struct track_info *info;
1845 editable_lock();
1846 info = play_queue_remove();
1847 if (info == NULL) {
1848 if (play_library) {
1849 info = lib_set_next();
1850 } else {
1851 info = pl_set_next();
1854 editable_unlock();
1856 if (info == NULL)
1857 return -1;
1859 *ti = info;
1860 return 0;
1863 static const struct player_callbacks player_callbacks = {
1864 .get_next = get_next
1867 static void init_curses(void)
1869 struct sigaction act;
1870 char *ptr, *term;
1872 sigemptyset(&act.sa_mask);
1873 act.sa_flags = 0;
1874 act.sa_handler = sig_int;
1875 sigaction(SIGINT, &act, NULL);
1877 sigemptyset(&act.sa_mask);
1878 act.sa_flags = 0;
1879 act.sa_handler = sig_winch;
1880 sigaction(SIGWINCH, &act, NULL);
1882 initscr();
1884 /* turn off kb buffering */
1885 cbreak();
1887 keypad(stdscr, TRUE);
1889 /* wait max 5 * 0.1 s if there are no keys available
1890 * doesn't really matter because we use select()
1892 halfdelay(5);
1894 noecho();
1895 if (has_colors()) {
1896 start_color();
1897 use_default_colors();
1899 d_print("Number of supported colors: %d\n", COLORS);
1900 ui_initialized = 1;
1902 /* this was disabled while initializing because it needs to be
1903 * called only once after all colors have been set
1905 update_colors();
1907 ptr = print_buffer;
1908 t_ts = tgetstr("ts", &ptr);
1909 t_fs = tgetstr("fs", &ptr);
1910 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
1912 if (!t_fs)
1913 t_ts = NULL;
1915 if (!t_ts && (term = getenv("TERM"))) {
1917 * Eterm: Eterm
1918 * aterm: rxvt
1919 * mlterm: xterm
1920 * terminal (xfce): xterm
1921 * urxvt: rxvt-unicode
1922 * xterm: xterm, xterm-{,16,88,256}color
1924 if (!strcmp(term, "screen")) {
1925 t_ts = "\033_";
1926 t_fs = "\033\\";
1927 } else if (!strncmp(term, "xterm", 5) ||
1928 !strncmp(term, "rxvt", 4) ||
1929 !strcmp(term, "Eterm")) {
1930 /* \033]1; change icon
1931 * \033]2; change title
1932 * \033]0; change both
1934 t_ts = "\033]0;";
1935 t_fs = "\007";
1940 static void init_all(void)
1942 server_init(server_address);
1944 /* does not select output plugin */
1945 player_init(&player_callbacks);
1947 /* plugins have been loaded so we know what plugin options are available */
1948 options_add();
1950 lib_init();
1951 searchable = tree_searchable;
1952 pl_init();
1953 cmus_init();
1954 browser_init();
1955 filters_init();
1956 help_init();
1957 cmdline_init();
1958 commands_init();
1959 search_mode_init();
1961 /* almost everything must be initialized now */
1962 options_load();
1964 /* finally we can set the output plugin */
1965 player_set_op(output_plugin);
1967 player_get_volume(&player_info.vol_left, &player_info.vol_right);
1969 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
1970 pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
1971 pl_filename = xstrdup(pl_autosave_filename);
1972 lib_filename = xstrdup(lib_autosave_filename);
1974 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
1975 cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);
1977 if (error_count) {
1978 char buf[16];
1980 warn("Press <enter> to continue.");
1981 fgets(buf, sizeof(buf), stdin);
1983 help_add_all_unbound();
1985 init_curses();
1988 static void exit_all(void)
1990 endwin();
1992 options_exit();
1994 server_exit();
1995 cmus_exit();
1996 cmus_save(lib_for_each, lib_autosave_filename);
1997 cmus_save(pl_for_each, pl_autosave_filename);
1999 player_exit();
2000 commands_exit();
2001 search_mode_exit();
2002 filters_exit();
2003 help_exit();
2004 browser_exit();
2007 enum {
2008 FLAG_LISTEN,
2009 FLAG_PLUGINS,
2010 FLAG_HELP,
2011 FLAG_VERSION,
2012 NR_FLAGS
2015 static struct option options[NR_FLAGS + 1] = {
2016 { 0, "listen", 1 },
2017 { 0, "plugins", 0 },
2018 { 0, "help", 0 },
2019 { 0, "version", 0 },
2020 { 0, NULL, 0 }
2023 static const char *usage =
2024 "Usage: %s [OPTION]...\n"
2025 "Curses based music player.\n"
2026 "\n"
2027 " --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
2028 " ADDR is either a UNIX socket or host[:port]\n"
2029 " --plugins list available plugins and exit\n"
2030 " --help display this help and exit\n"
2031 " --version " VERSION "\n"
2032 "\n"
2033 "Use cmus-remote to control cmus from command line.\n"
2034 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2036 int main(int argc, char *argv[])
2038 int list_plugins = 0;
2040 program_name = argv[0];
2041 argv++;
2042 while (1) {
2043 int idx;
2044 char *arg;
2046 idx = get_option(&argv, options, &arg);
2047 if (idx < 0)
2048 break;
2050 switch (idx) {
2051 case FLAG_HELP:
2052 printf(usage, program_name);
2053 return 0;
2054 case FLAG_VERSION:
2055 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
2056 return 0;
2057 case FLAG_PLUGINS:
2058 list_plugins = 1;
2059 break;
2060 case FLAG_LISTEN:
2061 server_address = xstrdup(arg);
2062 break;
2066 setlocale(LC_CTYPE, "");
2067 #ifdef CODESET
2068 charset = nl_langinfo(CODESET);
2069 #else
2070 charset = "ISO-8859-1";
2071 #endif
2072 if (strcmp(charset, "UTF-8") == 0) {
2073 using_utf8 = 1;
2074 } else {
2075 using_utf8 = 0;
2077 misc_init();
2078 if (server_address == NULL)
2079 server_address = xstrjoin(cmus_config_dir, "/socket");
2080 debug_init();
2081 d_print("charset = '%s'\n", charset);
2083 player_load_plugins();
2084 if (list_plugins) {
2085 player_dump_plugins();
2086 return 0;
2088 init_all();
2089 main_loop();
2090 exit_all();
2091 return 0;