1 /*
2 * Copyright 2008-2013 Various Authors
3 * Copyright 2004-2005 Timo Hirvonen
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "job.h"
20 #include "convert.h"
21 #include "ui_curses.h"
22 #include "cmdline.h"
23 #include "search_mode.h"
24 #include "command_mode.h"
25 #include "options.h"
26 #include "play_queue.h"
27 #include "browser.h"
28 #include "filters.h"
29 #include "cmus.h"
30 #include "player.h"
31 #include "output.h"
32 #include "utils.h"
33 #include "lib.h"
34 #include "pl.h"
35 #include "xmalloc.h"
36 #include "xstrjoin.h"
37 #include "window.h"
38 #include "comment.h"
39 #include "misc.h"
40 #include "prog.h"
41 #include "uchar.h"
42 #include "spawn.h"
43 #include "server.h"
44 #include "keys.h"
45 #include "debug.h"
46 #include "help.h"
47 #include "worker.h"
48 #include "input.h"
49 #include "file.h"
50 #include "path.h"
51 #include "mixer.h"
52 #include "mpris.h"
53 #include "locking.h"
54 #ifdef HAVE_CONFIG
55 #include "config/curses.h"
56 #include "config/iconv.h"
57 #endif
58
59 #include <unistd.h>
60 #include <fcntl.h>
61 #include <stdlib.h>
62 #include <stdio.h>
63 #include <errno.h>
64 #include <sys/ioctl.h>
65 #include <sys/select.h>
66 #include <ctype.h>
67 #include <dirent.h>
68 #include <locale.h>
69 #include <langinfo.h>
70 #ifdef HAVE_ICONV
71 #include <iconv.h>
72 #endif
73 #include <signal.h>
74 #include <stdarg.h>
75 #include <math.h>
76 #include <sys/time.h>
77
78 #if defined(__sun__) || defined(__CYGWIN__)
79 /* TIOCGWINSZ */
80 #include <termios.h>
81 #include <ncurses.h>
82 #else
83 #include <curses.h>
84 #endif
85
86 /* defined in <term.h> but without const */
87 char *tgetstr(const char *id, char **area);
88 char *tgoto(const char *cap, int col, int row);
89
90 /* globals. documented in ui_curses.h */
91
92 volatile sig_atomic_t cmus_running = 1;
93 int ui_initialized = 0;
94 enum ui_input_mode input_mode = NORMAL_MODE;
95 int cur_view = TREE_VIEW;
96 int prev_view = -1;
97 struct searchable *searchable;
98 char *lib_filename = NULL;
99 char *lib_ext_filename = NULL;
100 char *play_queue_filename = NULL;
101 char *play_queue_ext_filename = NULL;
102 char *charset = NULL;
103 int using_utf8 = 0;
104
105 /* ------------------------------------------------------------------------- */
106
107 static char *lib_autosave_filename;
108 static char *play_queue_autosave_filename;
109
110 /* shown error message and time stamp
111 * error is cleared if it is older than 3s and key was pressed
112 */
113 static char error_buf[512];
114 static time_t error_time = 0;
115 /* info messages are displayed in different color */
116 static int msg_is_error;
117 static int error_count = 0;
118
119 static char *server_address = NULL;
120
121 static char print_buffer[1024];
122
123 /* destination buffer for utf8_encode_to_buf and utf8_decode */
124 static char conv_buffer[512];
125
126 /* one character can take up to 4 bytes in UTF-8 */
127 #define print_buffer_max_width (sizeof(print_buffer) / 4 - 1)
128
129 /* used for messages to the client */
130 static int client_fd = -1;
131
132 static char tcap_buffer[64];
133 static const char *t_ts;
134 static const char *t_fs;
135
136 static int tree_win_x = 0;
137 static int tree_win_w = 0;
138
139 static int track_win_x = 0;
140 static int track_win_w = 0;
141
142 static int editable_win_x = 0;
143 static int editable_win_w = 0;
144 static int editable_active = 1;
145
146 static int show_cursor;
147 static int cursor_x;
148 static int cursor_y;
149
150 static const int default_esc_delay = 25;
151
152 static char *title_buf = NULL;
153
154 enum {
155 CURSED_WIN,
156 CURSED_WIN_CUR,
157 CURSED_WIN_SEL,
158 CURSED_WIN_SEL_CUR,
159
160 CURSED_WIN_ACTIVE,
161 CURSED_WIN_ACTIVE_CUR,
162 CURSED_WIN_ACTIVE_SEL,
163 CURSED_WIN_ACTIVE_SEL_CUR,
164
165 CURSED_SEPARATOR,
166 CURSED_WIN_TITLE,
167 CURSED_COMMANDLINE,
168 CURSED_STATUSLINE,
169
170 CURSED_TITLELINE,
171 CURSED_DIR,
172 CURSED_ERROR,
173 CURSED_INFO,
174
175 CURSED_TRACKWIN_ALBUM,
176
177 NR_CURSED
178 };
179
180 static unsigned char cursed_to_bg_idx[NR_CURSED] = {
181 COLOR_WIN_BG,
182 COLOR_WIN_BG,
183 COLOR_WIN_INACTIVE_SEL_BG,
184 COLOR_WIN_INACTIVE_CUR_SEL_BG,
185
186 COLOR_WIN_BG,
187 COLOR_WIN_BG,
188 COLOR_WIN_SEL_BG,
189 COLOR_WIN_CUR_SEL_BG,
190
191 COLOR_WIN_BG,
192 COLOR_WIN_TITLE_BG,
193 COLOR_CMDLINE_BG,
194 COLOR_STATUSLINE_BG,
195
196 COLOR_TITLELINE_BG,
197 COLOR_WIN_BG,
198 COLOR_CMDLINE_BG,
199 COLOR_CMDLINE_BG,
200
201 COLOR_TRACKWIN_ALBUM_BG,
202 };
203
204 static unsigned char cursed_to_fg_idx[NR_CURSED] = {
205 COLOR_WIN_FG,
206 COLOR_WIN_CUR,
207 COLOR_WIN_INACTIVE_SEL_FG,
208 COLOR_WIN_INACTIVE_CUR_SEL_FG,
209
210 COLOR_WIN_FG,
211 COLOR_WIN_CUR,
212 COLOR_WIN_SEL_FG,
213 COLOR_WIN_CUR_SEL_FG,
214
215 COLOR_SEPARATOR,
216 COLOR_WIN_TITLE_FG,
217 COLOR_CMDLINE_FG,
218 COLOR_STATUSLINE_FG,
219
220 COLOR_TITLELINE_FG,
221 COLOR_WIN_DIR,
222 COLOR_ERROR,
223 COLOR_INFO,
224
225 COLOR_TRACKWIN_ALBUM_FG,
226 };
227
228 static unsigned char cursed_to_attr_idx[NR_CURSED] = {
229 COLOR_WIN_ATTR,
230 COLOR_WIN_CUR_ATTR,
231 COLOR_WIN_INACTIVE_SEL_ATTR,
232 COLOR_WIN_INACTIVE_CUR_SEL_ATTR,
233
234 COLOR_WIN_ATTR,
235 COLOR_WIN_CUR_ATTR,
236 COLOR_WIN_SEL_ATTR,
237 COLOR_WIN_CUR_SEL_ATTR,
238
239 COLOR_WIN_ATTR,
240 COLOR_WIN_TITLE_ATTR,
241 COLOR_CMDLINE_ATTR,
242 COLOR_STATUSLINE_ATTR,
243
244 COLOR_TITLELINE_ATTR,
245 COLOR_WIN_ATTR,
246 COLOR_CMDLINE_ATTR,
247 COLOR_CMDLINE_ATTR,
248
249 COLOR_TRACKWIN_ALBUM_ATTR,
250 };
251
252 /* index is CURSED_*, value is fucking color pair */
253 static int pairs[NR_CURSED];
254
255 enum {
256 TF_ALBUMARTIST,
257 TF_ARTIST,
258 TF_ALBUM,
259 TF_DISC,
260 TF_TRACK,
261 TF_TITLE,
262 TF_PLAY_COUNT,
263 TF_YEAR,
264 TF_MAX_YEAR,
265 TF_ORIGINALYEAR,
266 TF_GENRE,
267 TF_COMMENT,
268 TF_DURATION,
269 TF_DURATION_SEC,
270 TF_ALBUMDURATION,
271 TF_BITRATE,
272 TF_CODEC,
273 TF_CODEC_PROFILE,
274 TF_PATHFILE,
275 TF_FILE,
276 TF_RG_TRACK_GAIN,
277 TF_RG_TRACK_PEAK,
278 TF_RG_ALBUM_GAIN,
279 TF_RG_ALBUM_PEAK,
280 TF_ARRANGER,
281 TF_COMPOSER,
282 TF_CONDUCTOR,
283 TF_LYRICIST,
284 TF_PERFORMER,
285 TF_REMIXER,
286 TF_LABEL,
287 TF_PUBLISHER,
288 TF_WORK,
289 TF_OPUS,
290 TF_PARTNUMBER,
291 TF_PART,
292 TF_SUBTITLE,
293 TF_MEDIA,
294 TF_VA,
295 TF_STATUS,
296 TF_POSITION,
297 TF_POSITION_SEC,
298 TF_TOTAL,
299 TF_VOLUME,
300 TF_LVOLUME,
301 TF_RVOLUME,
302 TF_BUFFER,
303 TF_REPEAT,
304 TF_CONTINUE,
305 TF_FOLLOW,
306 TF_SHUFFLE,
307 TF_PLAYLISTMODE,
308 TF_BPM,
309
310 NR_TFS
311 };
312
313 static struct format_option track_fopts[NR_TFS + 1] = {
314 DEF_FO_STR('A', "albumartist", 0),
315 DEF_FO_STR('a', "artist", 0),
316 DEF_FO_STR('l', "album", 0),
317 DEF_FO_INT('D', "discnumber", 1),
318 DEF_FO_INT('n', "tracknumber", 1),
319 DEF_FO_STR('t', "title", 0),
320 DEF_FO_INT('X', "play_count", 0),
321 DEF_FO_INT('y', "date", 1),
322 DEF_FO_INT('\0', "maxdate", 1),
323 DEF_FO_INT('\0', "originaldate", 1),
324 DEF_FO_STR('g', "genre", 0),
325 DEF_FO_STR('c', "comment", 0),
326 DEF_FO_TIME('d', "duration", 0),
327 DEF_FO_INT('\0', "duration_sec", 1),
328 DEF_FO_TIME('\0', "albumduration", 0),
329 DEF_FO_INT('\0', "bitrate", 0),
330 DEF_FO_STR('\0', "codec", 0),
331 DEF_FO_STR('\0', "codec_profile", 0),
332 DEF_FO_STR('f', "path", 0),
333 DEF_FO_STR('F', "filename", 0),
334 DEF_FO_DOUBLE('\0', "rg_track_gain", 0),
335 DEF_FO_DOUBLE('\0', "rg_track_peak", 0),
336 DEF_FO_DOUBLE('\0', "rg_album_gain", 0),
337 DEF_FO_DOUBLE('\0', "rg_album_peak", 0),
338 DEF_FO_STR('\0', "arranger", 0),
339 DEF_FO_STR('\0', "composer", 0),
340 DEF_FO_STR('\0', "conductor", 0),
341 DEF_FO_STR('\0', "lyricist", 0),
342 DEF_FO_STR('\0', "performer", 0),
343 DEF_FO_STR('\0', "remixer", 0),
344 DEF_FO_STR('\0', "label", 0),
345 DEF_FO_STR('\0', "publisher", 0),
346 DEF_FO_STR('\0', "work", 0),
347 DEF_FO_STR('\0', "opus", 0),
348 DEF_FO_STR('\0', "partnumber", 0),
349 DEF_FO_STR('\0', "part", 0),
350 DEF_FO_STR('\0', "subtitle", 0),
351 DEF_FO_STR('\0', "media", 0),
352 DEF_FO_INT('\0', "va", 0),
353 DEF_FO_STR('\0', "status", 0),
354 DEF_FO_TIME('\0', "position", 0),
355 DEF_FO_INT('\0', "position_sec", 1),
356 DEF_FO_TIME('\0', "total", 0),
357 DEF_FO_INT('\0', "volume", 1),
358 DEF_FO_INT('\0', "lvolume", 1),
359 DEF_FO_INT('\0', "rvolume", 1),
360 DEF_FO_INT('\0', "buffer", 1),
361 DEF_FO_STR('\0', "repeat", 0),
362 DEF_FO_STR('\0', "continue", 0),
363 DEF_FO_STR('\0', "follow", 0),
364 DEF_FO_STR('\0', "shuffle", 0),
365 DEF_FO_STR('\0', "playlist_mode", 0),
366 DEF_FO_INT('\0', "bpm", 0),
367 DEF_FO_END
368 };
369
get_track_win_x(void)370 int get_track_win_x(void)
371 {
372 return track_win_x;
373 }
374
track_format_valid(const char * format)375 int track_format_valid(const char *format)
376 {
377 return format_valid(format, track_fopts);
378 }
379
utf8_encode_to_buf(const char * buffer)380 static void utf8_encode_to_buf(const char *buffer)
381 {
382 int n;
383 #ifdef HAVE_ICONV
384 static iconv_t cd = (iconv_t)-1;
385 size_t is, os;
386 const char *i;
387 char *o;
388 int rc;
389
390 if (cd == (iconv_t)-1) {
391 d_print("iconv_open(UTF-8, %s)\n", charset);
392 cd = iconv_open("UTF-8", charset);
393 if (cd == (iconv_t)-1) {
394 d_print("iconv_open failed: %s\n", strerror(errno));
395 goto fallback;
396 }
397 }
398 i = buffer;
399 o = conv_buffer;
400 is = strlen(i);
401 os = sizeof(conv_buffer) - 1;
402 rc = iconv(cd, (void *)&i, &is, &o, &os);
403 *o = 0;
404 if (rc == -1) {
405 d_print("iconv failed: %s\n", strerror(errno));
406 goto fallback;
407 }
408 return;
409 fallback:
410 #endif
411 n = min_i(sizeof(conv_buffer) - 1, strlen(buffer));
412 memmove(conv_buffer, buffer, n);
413 conv_buffer[n] = '\0';
414 }
415
utf8_decode(const char * buffer)416 static void utf8_decode(const char *buffer)
417 {
418 int n;
419 #ifdef HAVE_ICONV
420 static iconv_t cd = (iconv_t)-1;
421 size_t is, os;
422 const char *i;
423 char *o;
424 int rc;
425
426 if (cd == (iconv_t)-1) {
427 d_print("iconv_open(%s, UTF-8)\n", charset);
428 cd = iconv_open(charset, "UTF-8");
429 if (cd == (iconv_t)-1) {
430 d_print("iconv_open failed: %s\n", strerror(errno));
431 goto fallback;
432 }
433 }
434 i = buffer;
435 o = conv_buffer;
436 is = strlen(i);
437 os = sizeof(conv_buffer) - 1;
438 rc = iconv(cd, (void *)&i, &is, &o, &os);
439 *o = 0;
440 if (rc == -1) {
441 d_print("iconv failed: %s\n", strerror(errno));
442 goto fallback;
443 }
444 return;
445 fallback:
446 #endif
447 n = u_to_ascii(conv_buffer, buffer, sizeof(conv_buffer) - 1);
448 conv_buffer[n] = '\0';
449 }
450
451 /* screen updates {{{ */
452
dump_print_buffer(int row,int col)453 static void dump_print_buffer(int row, int col)
454 {
455 if (using_utf8) {
456 (void) mvaddstr(row, col, print_buffer);
457 } else {
458 utf8_decode(print_buffer);
459 (void) mvaddstr(row, col, conv_buffer);
460 }
461 }
462
463 /* print @str into @buf
464 *
465 * if @str is shorter than @width pad with spaces
466 * if @str is wider than @width truncate and add "..."
467 */
format_str(char * buf,const char * str,int width)468 static int format_str(char *buf, const char *str, int width)
469 {
470 int s = 0, ellipsis_pos = 0, cut_double_width = 0;
471 size_t d = 0;
472
473 while (1) {
474 uchar u;
475 int w;
476
477 u = u_get_char(str, &s);
478 if (u == 0) {
479 memset(buf + d, ' ', width);
480 d += width;
481 break;
482 }
483
484 w = u_char_width(u);
485 if (width == 3)
486 ellipsis_pos = d;
487 if (width == 4 && w == 2) {
488 /* can't cut double-width char */
489 ellipsis_pos = d + 1;
490 cut_double_width = 1;
491 }
492
493 width -= w;
494 if (width < 0) {
495 /* does not fit */
496 d = ellipsis_pos;
497 if (cut_double_width) {
498 /* first half of the double-width char */
499 buf[d - 1] = ' ';
500 }
501 buf[d++] = '.';
502 buf[d++] = '.';
503 buf[d++] = '.';
504 break;
505 }
506 u_set_char(buf, &d, u);
507 }
508 return d;
509 }
510
sprint(int row,int col,const char * str,int width)511 static void sprint(int row, int col, const char *str, int width)
512 {
513 int pos = 0;
514
515 print_buffer[pos++] = ' ';
516 pos += format_str(print_buffer + pos, str, width - 2);
517 print_buffer[pos++] = ' ';
518 print_buffer[pos] = 0;
519 dump_print_buffer(row, col);
520 }
521
sprint_ascii(int row,int col,const char * str,int len)522 static void sprint_ascii(int row, int col, const char *str, int len)
523 {
524 int l;
525
526 l = strlen(str);
527 len -= 2;
528
529 print_buffer[0] = ' ';
530 if (l > len) {
531 memcpy(print_buffer + 1, str, len - 3);
532 print_buffer[len - 2] = '.';
533 print_buffer[len - 1] = '.';
534 print_buffer[len - 0] = '.';
535 } else {
536 memcpy(print_buffer + 1, str, l);
537 memset(print_buffer + 1 + l, ' ', len - l);
538 }
539 print_buffer[len + 1] = ' ';
540 print_buffer[len + 2] = 0;
541 (void) mvaddstr(row, col, print_buffer);
542 }
543
fopt_set_str(struct format_option * fopt,const char * str)544 static inline void fopt_set_str(struct format_option *fopt, const char *str)
545 {
546 BUG_ON(fopt->type != FO_STR);
547 if (str) {
548 fopt->fo_str = str;
549 fopt->empty = 0;
550 } else {
551 fopt->empty = 1;
552 }
553 }
554
fopt_set_int(struct format_option * fopt,int value,int empty)555 static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
556 {
557 BUG_ON(fopt->type != FO_INT);
558 fopt->fo_int = value;
559 fopt->empty = empty;
560 }
561
fopt_set_double(struct format_option * fopt,double value,int empty)562 static inline void fopt_set_double(struct format_option *fopt, double value, int empty)
563 {
564 BUG_ON(fopt->type != FO_DOUBLE);
565 fopt->fo_double = value;
566 fopt->empty = empty;
567 }
568
fopt_set_time(struct format_option * fopt,int value,int empty)569 static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
570 {
571 BUG_ON(fopt->type != FO_TIME);
572 fopt->fo_time = value;
573 fopt->empty = empty;
574 }
575
fill_track_fopts_track_info(struct track_info * info)576 static void fill_track_fopts_track_info(struct track_info *info)
577 {
578 char *filename;
579
580 if (using_utf8) {
581 filename = info->filename;
582 } else {
583 utf8_encode_to_buf(info->filename);
584 filename = conv_buffer;
585 }
586
587 fopt_set_str(&track_fopts[TF_ALBUMARTIST], info->albumartist);
588 fopt_set_str(&track_fopts[TF_ARTIST], info->artist);
589 fopt_set_str(&track_fopts[TF_ALBUM], info->album);
590 fopt_set_int(&track_fopts[TF_PLAY_COUNT], info->play_count, 0);
591 fopt_set_int(&track_fopts[TF_DISC], info->discnumber, info->discnumber == -1);
592 fopt_set_int(&track_fopts[TF_TRACK], info->tracknumber, info->tracknumber == -1);
593 fopt_set_str(&track_fopts[TF_TITLE], info->title);
594 fopt_set_int(&track_fopts[TF_YEAR], info->date / 10000, info->date <= 0);
595 fopt_set_str(&track_fopts[TF_GENRE], info->genre);
596 fopt_set_str(&track_fopts[TF_COMMENT], info->comment);
597 fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
598 fopt_set_int(&track_fopts[TF_DURATION_SEC], info->duration, info->duration == -1);
599 fopt_set_double(&track_fopts[TF_RG_TRACK_GAIN], info->rg_track_gain, isnan(info->rg_track_gain));
600 fopt_set_double(&track_fopts[TF_RG_TRACK_PEAK], info->rg_track_peak, isnan(info->rg_track_peak));
601 fopt_set_double(&track_fopts[TF_RG_ALBUM_GAIN], info->rg_album_gain, isnan(info->rg_album_gain));
602 fopt_set_double(&track_fopts[TF_RG_ALBUM_PEAK], info->rg_album_peak, isnan(info->rg_album_peak));
603 fopt_set_int(&track_fopts[TF_ORIGINALYEAR], info->originaldate / 10000, info->originaldate <= 0);
604 fopt_set_int(&track_fopts[TF_BITRATE], (int) (info->bitrate / 1000. + 0.5), info->bitrate == -1);
605 fopt_set_str(&track_fopts[TF_CODEC], info->codec);
606 fopt_set_str(&track_fopts[TF_CODEC_PROFILE], info->codec_profile);
607 fopt_set_str(&track_fopts[TF_PATHFILE], filename);
608 fopt_set_str(&track_fopts[TF_ARRANGER], keyvals_get_val(info->comments, "arranger"));
609 fopt_set_str(&track_fopts[TF_COMPOSER], keyvals_get_val(info->comments, "composer"));
610 fopt_set_str(&track_fopts[TF_CONDUCTOR], keyvals_get_val(info->comments, "conductor"));
611 fopt_set_str(&track_fopts[TF_LYRICIST], keyvals_get_val(info->comments, "lyricist"));
612 fopt_set_str(&track_fopts[TF_PERFORMER], keyvals_get_val(info->comments, "performer"));
613 fopt_set_str(&track_fopts[TF_REMIXER], keyvals_get_val(info->comments, "remixer"));
614 fopt_set_str(&track_fopts[TF_LABEL], keyvals_get_val(info->comments, "label"));
615 fopt_set_str(&track_fopts[TF_PUBLISHER], keyvals_get_val(info->comments, "publisher"));
616 fopt_set_str(&track_fopts[TF_WORK], keyvals_get_val(info->comments, "work"));
617 fopt_set_str(&track_fopts[TF_OPUS], keyvals_get_val(info->comments, "opus"));
618 fopt_set_str(&track_fopts[TF_PARTNUMBER], keyvals_get_val(info->comments, "partnumber"));
619 fopt_set_str(&track_fopts[TF_PART], keyvals_get_val(info->comments, "part"));
620 fopt_set_str(&track_fopts[TF_SUBTITLE], keyvals_get_val(info->comments, "subtitle"));
621 fopt_set_str(&track_fopts[TF_MEDIA], info->media);
622 fopt_set_int(&track_fopts[TF_VA], 0, !track_is_compilation(info->comments));
623 if (is_http_url(info->filename)) {
624 fopt_set_str(&track_fopts[TF_FILE], filename);
625 } else {
626 fopt_set_str(&track_fopts[TF_FILE], path_basename(filename));
627 }
628 fopt_set_int(&track_fopts[TF_BPM], info->bpm, info->bpm == -1);
629 }
630
get_album_length(struct album * album)631 static int get_album_length(struct album *album)
632 {
633 struct tree_track *track;
634 struct rb_node *tmp;
635 int duration = 0;
636
637 rb_for_each_entry(track, tmp, &album->track_root, tree_node) {
638 duration += tree_track_info(track)->duration;
639 }
640
641 return duration;
642 }
643
fill_track_fopts_album(struct album * album)644 static void fill_track_fopts_album(struct album *album)
645 {
646 fopt_set_int(&track_fopts[TF_YEAR], album->min_date / 10000, album->min_date <= 0);
647 fopt_set_int(&track_fopts[TF_MAX_YEAR], album->date / 10000, album->date <= 0);
648 fopt_set_str(&track_fopts[TF_ALBUMARTIST], album->artist->name);
649 fopt_set_str(&track_fopts[TF_ARTIST], album->artist->name);
650 fopt_set_str(&track_fopts[TF_ALBUM], album->name);
651 fopt_set_time(&track_fopts[TF_ALBUMDURATION], get_album_length(album), 0);
652 }
653
fill_track_fopts_artist(struct artist * artist)654 static void fill_track_fopts_artist(struct artist *artist)
655 {
656 const char *name = display_artist_sort_name ? artist_sort_name(artist) : artist->name;
657 fopt_set_str(&track_fopts[TF_ARTIST], name);
658 fopt_set_str(&track_fopts[TF_ALBUMARTIST], name);
659 }
660
get_global_fopts(void)661 const struct format_option *get_global_fopts(void)
662 {
663 if (player_info.ti)
664 fill_track_fopts_track_info(player_info.ti);
665
666 static const char *status_strs[] = { ".", ">", "|" };
667 static const char *cont_strs[] = { " ", "C" };
668 static const char *follow_strs[] = { " ", "F" };
669 static const char *repeat_strs[] = { " ", "R" };
670 static const char *shuffle_strs[] = { " ", "S" };
671 int buffer_fill, vol, vol_left, vol_right;
672 int duration = -1;
673
674 fopt_set_time(&track_fopts[TF_TOTAL], play_library ? lib_editable.total_time :
675 pl_playing_total_time(), 0);
676
677 fopt_set_str(&track_fopts[TF_FOLLOW], follow_strs[follow]);
678 fopt_set_str(&track_fopts[TF_REPEAT], repeat_strs[repeat]);
679 fopt_set_str(&track_fopts[TF_SHUFFLE], shuffle_strs[shuffle]);
680 fopt_set_str(&track_fopts[TF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
681
682 if (player_info.ti)
683 duration = player_info.ti->duration;
684
685 vol_left = vol_right = vol = -1;
686 if (soft_vol) {
687 vol_left = soft_vol_l;
688 vol_right = soft_vol_r;
689 vol = (vol_left + vol_right + 1) / 2;
690 } else if (volume_max && volume_l >= 0 && volume_r >= 0) {
691 vol_left = scale_to_percentage(volume_l, volume_max);
692 vol_right = scale_to_percentage(volume_r, volume_max);
693 vol = (vol_left + vol_right + 1) / 2;
694 }
695 buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
696
697 fopt_set_str(&track_fopts[TF_STATUS], status_strs[player_info.status]);
698
699 if (show_remaining_time && duration != -1) {
700 fopt_set_time(&track_fopts[TF_POSITION], player_info.pos - duration, 0);
701 } else {
702 fopt_set_time(&track_fopts[TF_POSITION], player_info.pos, 0);
703 }
704
705 fopt_set_int(&track_fopts[TF_POSITION_SEC], player_info.pos, player_info.pos < 0);
706 fopt_set_time(&track_fopts[TF_DURATION], duration, duration < 0);
707 fopt_set_int(&track_fopts[TF_VOLUME], vol, vol < 0);
708 fopt_set_int(&track_fopts[TF_LVOLUME], vol_left, vol_left < 0);
709 fopt_set_int(&track_fopts[TF_RVOLUME], vol_right, vol_right < 0);
710 fopt_set_int(&track_fopts[TF_BUFFER], buffer_fill, 0);
711 fopt_set_str(&track_fopts[TF_CONTINUE], cont_strs[player_cont]);
712 fopt_set_int(&track_fopts[TF_BITRATE], player_info.current_bitrate / 1000. + 0.5, 0);
713
714 return track_fopts;
715 }
716
print_tree(struct window * win,int row,struct iter * iter)717 static void print_tree(struct window *win, int row, struct iter *iter)
718 {
719 struct artist *artist;
720 struct album *album;
721 struct iter sel;
722 int current, selected, active, pos;
723
724 artist = iter_to_artist(iter);
725 album = iter_to_album(iter);
726 current = 0;
727 if (lib_cur_track) {
728 if (album) {
729 current = CUR_ALBUM == album;
730 } else {
731 current = CUR_ARTIST == artist;
732 }
733 }
734 window_get_sel(win, &sel);
735 selected = iters_equal(iter, &sel);
736 active = lib_cur_win == lib_tree_win;
737 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
738
739 if (active && selected) {
740 cursor_x = 0;
741 cursor_y = 1 + row;
742 }
743
744 print_buffer[0] = ' ';
745 if (album) {
746 fill_track_fopts_album(album);
747 format_print(print_buffer + 1, tree_win_w - 2, tree_win_format, track_fopts);
748 } else {
749 fill_track_fopts_artist(artist);
750 format_print(print_buffer + 1, tree_win_w - 2, tree_win_artist_format, track_fopts);
751 }
752 pos = strlen(print_buffer);
753 print_buffer[pos++] = ' ';
754 print_buffer[pos++] = 0;
755 dump_print_buffer(row + 1, tree_win_x);
756 }
757
print_track(struct window * win,int row,struct iter * iter)758 static void print_track(struct window *win, int row, struct iter *iter)
759 {
760 struct tree_track *track;
761 struct album *album;
762 struct track_info *ti;
763 struct iter sel;
764 int current, selected, active;
765 const char *format;
766
767 track = iter_to_tree_track(iter);
768 album = iter_to_album(iter);
769
770 if (track == (struct tree_track*)album) {
771 int pos;
772 struct fp_len len;
773
774 bkgdset(pairs[CURSED_TRACKWIN_ALBUM]);
775
776 fill_track_fopts_album(album);
777
778 len = format_print(print_buffer, track_win_w, track_win_album_format, track_fopts);
779 dump_print_buffer(row + 1, track_win_x);
780
781 bkgdset(pairs[CURSED_SEPARATOR]);
782 for(pos = track_win_x + len.llen; pos < COLS - len.rlen; ++pos)
783 (void) mvaddch(row + 1, pos, ACS_HLINE);
784
785 return;
786 }
787
788 current = lib_cur_track == track;
789 window_get_sel(win, &sel);
790 selected = iters_equal(iter, &sel);
791 active = lib_cur_win == lib_track_win;
792 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
793
794 if (active && selected) {
795 cursor_x = track_win_x;
796 cursor_y = 1 + row;
797 }
798
799 ti = tree_track_info(track);
800 fill_track_fopts_track_info(ti);
801
802 format = track_win_format;
803 if (track_info_has_tag(ti)) {
804 if (*track_win_format_va && track_is_compilation(ti->comments))
805 format = track_win_format_va;
806 } else if (*track_win_alt_format) {
807 format = track_win_alt_format;
808 }
809 format_print(print_buffer, track_win_w, format, track_fopts);
810 dump_print_buffer(row + 1, track_win_x);
811 }
812
813 /* used by print_editable only */
814 static struct simple_track *current_track;
815
print_editable(struct window * win,int row,struct iter * iter)816 static void print_editable(struct window *win, int row, struct iter *iter)
817 {
818 struct simple_track *track;
819 struct iter sel;
820 int current, selected, active;
821 const char *format;
822
823 track = iter_to_simple_track(iter);
824 current = current_track == track;
825 window_get_sel(win, &sel);
826 selected = iters_equal(iter, &sel);
827
828 if (selected) {
829 cursor_x = editable_win_x;
830 cursor_y = 1 + row;
831 }
832
833 active = editable_active;
834 if (!selected && track->marked) {
835 selected = 1;
836 active = 0;
837 }
838
839 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
840
841 fill_track_fopts_track_info(track->info);
842
843 format = list_win_format;
844 if (track_info_has_tag(track->info)) {
845 if (*list_win_format_va && track_is_compilation(track->info->comments))
846 format = list_win_format_va;
847 } else if (*list_win_alt_format) {
848 format = list_win_alt_format;
849 }
850 format_print(print_buffer, editable_win_w, format, track_fopts);
851 dump_print_buffer(row + 1, editable_win_x);
852 }
853
print_browser(struct window * win,int row,struct iter * iter)854 static void print_browser(struct window *win, int row, struct iter *iter)
855 {
856 struct browser_entry *e;
857 struct iter sel;
858 int selected;
859
860 e = iter_to_browser_entry(iter);
861 window_get_sel(win, &sel);
862 selected = iters_equal(iter, &sel);
863 if (selected) {
864 int active = 1;
865 int current = 0;
866
867 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
868 } else {
869 if (e->type == BROWSER_ENTRY_DIR) {
870 bkgdset(pairs[CURSED_DIR]);
871 } else {
872 bkgdset(pairs[CURSED_WIN]);
873 }
874 }
875
876 if (selected) {
877 cursor_x = 0;
878 cursor_y = 1 + row;
879 }
880
881 /* file name encoding == terminal encoding. no need to convert */
882 if (using_utf8) {
883 sprint(row + 1, 0, e->name, COLS);
884 } else {
885 sprint_ascii(row + 1, 0, e->name, COLS);
886 }
887 }
888
print_filter(struct window * win,int row,struct iter * iter)889 static void print_filter(struct window *win, int row, struct iter *iter)
890 {
891 char buf[256];
892 struct filter_entry *e = iter_to_filter_entry(iter);
893 struct iter sel;
894 /* window active? */
895 int active = 1;
896 /* row selected? */
897 int selected;
898 /* is the filter currently active? */
899 int current = !!e->act_stat;
900 const char stat_chars[3] = " *!";
901 int ch1, ch2, ch3, pos;
902 const char *e_filter;
903
904 window_get_sel(win, &sel);
905 selected = iters_equal(iter, &sel);
906 bkgdset(pairs[(active << 2) | (selected << 1) | current]);
907
908 if (selected) {
909 cursor_x = 0;
910 cursor_y = 1 + row;
911 }
912
913 ch1 = ' ';
914 ch3 = ' ';
915 if (e->sel_stat != e->act_stat) {
916 ch1 = '[';
917 ch3 = ']';
918 }
919 ch2 = stat_chars[e->sel_stat];
920
921 e_filter = e->filter;
922 if (!using_utf8) {
923 utf8_encode_to_buf(e_filter);
924 e_filter = conv_buffer;
925 }
926
927 snprintf(buf, sizeof(buf), "%c%c%c%-15s %.235s", ch1, ch2, ch3, e->name, e_filter);
928 pos = format_str(print_buffer, buf, COLS - 1);
929 print_buffer[pos++] = ' ';
930 print_buffer[pos] = 0;
931 dump_print_buffer(row + 1, 0);
932 }
933
print_help(struct window * win,int row,struct iter * iter)934 static void print_help(struct window *win, int row, struct iter *iter)
935 {
936 struct iter sel;
937 int selected;
938 int pos;
939 int active = 1;
940 char buf[OPTION_MAX_SIZE];
941 const struct help_entry *e = iter_to_help_entry(iter);
942 const struct cmus_opt *opt;
943
944 window_get_sel(win, &sel);
945 selected = iters_equal(iter, &sel);
946 bkgdset(pairs[(active << 2) | (selected << 1)]);
947
948 if (selected) {
949 cursor_x = 0;
950 cursor_y = 1 + row;
951 }
952
953 switch (e->type) {
954 case HE_TEXT:
955 snprintf(buf, sizeof(buf), " %s", e->text);
956 break;
957 case HE_BOUND:
958 snprintf(buf, sizeof(buf), " %-8s %-23s %s",
959 key_context_names[e->binding->ctx],
960 e->binding->key->name,
961 e->binding->cmd);
962 break;
963 case HE_UNBOUND:
964 snprintf(buf, sizeof(buf), " %s", e->command->name);
965 break;
966 case HE_OPTION:
967 opt = e->option;
968 snprintf(buf, sizeof(buf), " %-29s ", opt->name);
969 size_t len = strlen(buf);
970 opt->get(opt->data, buf + len, sizeof(buf) - len);
971 break;
972 }
973 pos = format_str(print_buffer, buf, COLS - 1);
974 print_buffer[pos++] = ' ';
975 print_buffer[pos] = 0;
976 dump_print_buffer(row + 1, 0);
977 }
978
update_window(struct window * win,int x,int y,int w,const char * title,void (* print)(struct window *,int,struct iter *))979 static void update_window(struct window *win, int x, int y, int w, const char *title,
980 void (*print)(struct window *, int, struct iter *))
981 {
982 struct iter iter;
983 int nr_rows;
984 int c, i;
985
986 win->changed = 0;
987
988 bkgdset(pairs[CURSED_WIN_TITLE]);
989 c = snprintf(print_buffer, w + 1, " %s", title);
990 if (c > w)
991 c = w;
992 memset(print_buffer + c, ' ', w - c + 1);
993 print_buffer[w] = 0;
994 dump_print_buffer(y, x);
995 nr_rows = window_get_nr_rows(win);
996 i = 0;
997 if (window_get_top(win, &iter)) {
998 while (i < nr_rows) {
999 print(win, i, &iter);
1000 i++;
1001 if (!window_get_next(win, &iter))
1002 break;
1003 }
1004 }
1005
1006 bkgdset(pairs[0]);
1007 memset(print_buffer, ' ', w);
1008 print_buffer[w] = 0;
1009 while (i < nr_rows) {
1010 dump_print_buffer(y + i + 1, x);
1011 i++;
1012 }
1013 }
1014
update_tree_window(void)1015 static void update_tree_window(void)
1016 {
1017 update_window(lib_tree_win, tree_win_x, 0, tree_win_w,
1018 "Artist / Album", print_tree);
1019 }
1020
update_track_window(void)1021 static void update_track_window(void)
1022 {
1023 char title[512];
1024
1025 /* it doesn't matter what format options we use because the format
1026 * string does not contain any format charaters */
1027 format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
1028 update_window(lib_track_win, track_win_x, 0, track_win_w, title,
1029 print_track);
1030 }
1031
print_pl_list(struct window * win,int row,struct iter * iter)1032 static void print_pl_list(struct window *win, int row, struct iter *iter)
1033 {
1034 struct pl_list_info info;
1035
1036 pl_list_iter_to_info(iter, &info);
1037
1038 bkgdset(pairs[(info.active<<2) | (info.selected<<1) | info.current]);
1039
1040 const char *prefix = " ";
1041 if (info.marked)
1042 prefix = " * ";
1043 size_t prefix_w = strlen(prefix);
1044 format_str(print_buffer, prefix, prefix_w);
1045
1046 if (tree_win_w >= prefix_w)
1047 format_str(print_buffer + prefix_w, info.name,
1048 tree_win_w - prefix_w);
1049
1050 dump_print_buffer(row + 1, 0);
1051 }
1052
update_pl_list(struct window * win)1053 static void update_pl_list(struct window *win)
1054 {
1055 update_window(win, tree_win_x, 0, tree_win_w, "Playlist",
1056 print_pl_list);
1057 }
1058
update_pl_tracks(struct window * win)1059 static void update_pl_tracks(struct window *win)
1060 {
1061 char title[512];
1062
1063 editable_win_x = track_win_x;
1064 editable_win_w = track_win_w;
1065 editable_active = pl_get_cursor_in_track_window();
1066
1067 get_global_fopts();
1068 fopt_set_time(&track_fopts[TF_TOTAL], pl_visible_total_time(), 0);
1069
1070 format_print(title, track_win_w - 2, "Track%=%{total}", track_fopts);
1071 update_window(win, track_win_x, 0, track_win_w, title, print_editable);
1072
1073 editable_active = 1;
1074 editable_win_x = 0;
1075 editable_win_w = COLS;
1076 }
1077
pretty(const char * path)1078 static const char *pretty(const char *path)
1079 {
1080 static int home_len = -1;
1081 static char buf[256];
1082
1083 if (home_len == -1)
1084 home_len = strlen(home_dir);
1085
1086 if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
1087 return path;
1088
1089 buf[0] = '~';
1090 strcpy(buf + 1, path + home_len);
1091 return buf;
1092 }
1093
1094 static const char * const sorted_names[2] = { "", "sorted by " };
1095
update_editable_window(struct editable * e,const char * title,const char * filename)1096 static void update_editable_window(struct editable *e, const char *title, const char *filename)
1097 {
1098 char buf[512];
1099 int pos;
1100
1101 if (filename) {
1102 if (using_utf8) {
1103 /* already UTF-8 */
1104 } else {
1105 utf8_encode_to_buf(filename);
1106 filename = conv_buffer;
1107 }
1108 snprintf(buf, sizeof(buf), "%s %.256s - %d tracks", title, pretty(filename), e->nr_tracks);
1109 } else {
1110 snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
1111 }
1112
1113 if (e->nr_marked) {
1114 pos = strlen(buf);
1115 snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
1116 }
1117 pos = strlen(buf);
1118 snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
1119 sorted_names[e->shared->sort_str[0] != 0],
1120 e->shared->sort_str);
1121
1122 update_window(e->shared->win, 0, 0, COLS, buf, &print_editable);
1123 }
1124
update_sorted_window(void)1125 static void update_sorted_window(void)
1126 {
1127 current_track = (struct simple_track *)lib_cur_track;
1128 update_editable_window(&lib_editable, "Library", lib_filename);
1129 }
1130
update_play_queue_window(void)1131 static void update_play_queue_window(void)
1132 {
1133 current_track = NULL;
1134 update_editable_window(&pq_editable, "Play Queue", NULL);
1135 }
1136
update_browser_window(void)1137 static void update_browser_window(void)
1138 {
1139 char title[512];
1140 char *dirname;
1141
1142 if (using_utf8) {
1143 /* already UTF-8 */
1144 dirname = browser_dir;
1145 } else {
1146 utf8_encode_to_buf(browser_dir);
1147 dirname = conv_buffer;
1148 }
1149 snprintf(title, sizeof(title), "Browser - %.501s", dirname);
1150 update_window(browser_win, 0, 0, COLS, title, print_browser);
1151 }
1152
update_filters_window(void)1153 static void update_filters_window(void)
1154 {
1155 update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
1156 }
1157
update_help_window(void)1158 static void update_help_window(void)
1159 {
1160 update_window(help_win, 0, 0, COLS, "Settings", print_help);
1161 }
1162
draw_separator(void)1163 static void draw_separator(void)
1164 {
1165 int row;
1166
1167 bkgdset(pairs[CURSED_WIN_TITLE]);
1168 (void) mvaddch(0, tree_win_w, ' ');
1169 bkgdset(pairs[CURSED_SEPARATOR]);
1170 for (row = 1; row < LINES - 3; row++)
1171 (void) mvaddch(row, tree_win_w, ACS_VLINE);
1172 }
1173
update_pl_view(int full)1174 static void update_pl_view(int full)
1175 {
1176 current_track = pl_get_playing_track();
1177 pl_draw(update_pl_list, update_pl_tracks, full);
1178 draw_separator();
1179 }
1180
do_update_view(int full)1181 static void do_update_view(int full)
1182 {
1183 cursor_x = -1;
1184 cursor_y = -1;
1185
1186 switch (cur_view) {
1187 case TREE_VIEW:
1188 if (full || lib_tree_win->changed)
1189 update_tree_window();
1190 if (full || lib_track_win->changed)
1191 update_track_window();
1192 draw_separator();
1193 update_filterline();
1194 break;
1195 case SORTED_VIEW:
1196 update_sorted_window();
1197 update_filterline();
1198 break;
1199 case PLAYLIST_VIEW:
1200 update_pl_view(full);
1201 break;
1202 case QUEUE_VIEW:
1203 update_play_queue_window();
1204 break;
1205 case BROWSER_VIEW:
1206 update_browser_window();
1207 break;
1208 case FILTERS_VIEW:
1209 update_filters_window();
1210 break;
1211 case HELP_VIEW:
1212 update_help_window();
1213 break;
1214 }
1215 }
1216
do_update_statusline(void)1217 static void do_update_statusline(void)
1218 {
1219 format_print(print_buffer, COLS, statusline_format, get_global_fopts());
1220 bkgdset(pairs[CURSED_STATUSLINE]);
1221 dump_print_buffer(LINES - 2, 0);
1222
1223 if (player_info.error_msg)
1224 error_msg("%s", player_info.error_msg);
1225 }
1226
dump_buffer(const char * buffer)1227 static void dump_buffer(const char *buffer)
1228 {
1229 if (using_utf8) {
1230 addstr(buffer);
1231 } else {
1232 utf8_decode(buffer);
1233 addstr(conv_buffer);
1234 }
1235 }
1236
do_update_commandline(void)1237 static void do_update_commandline(void)
1238 {
1239 char *str;
1240 int w;
1241 size_t idx;
1242 char ch;
1243
1244 move(LINES - 1, 0);
1245 if (error_buf[0]) {
1246 if (msg_is_error) {
1247 bkgdset(pairs[CURSED_ERROR]);
1248 } else {
1249 bkgdset(pairs[CURSED_INFO]);
1250 }
1251 addstr(error_buf);
1252 clrtoeol();
1253 return;
1254 }
1255 bkgdset(pairs[CURSED_COMMANDLINE]);
1256 if (input_mode == NORMAL_MODE) {
1257 clrtoeol();
1258 return;
1259 }
1260
1261 str = cmdline.line;
1262 if (!using_utf8) {
1263 /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
1264 * characters are invalid UTF-8 so it really is in locale's
1265 * encoding.
1266 *
1267 * This code should be safe because cmdline.bpos ==
1268 * cmdline.cpos as every non-ASCII character is counted as one
1269 * invalid UTF-8 byte.
1270 *
1271 * NOTE: This has nothing to do with widths of printed
1272 * characters. I.e. even if there were control characters
1273 * (displayed as <xx>) there would be no problem because bpos
1274 * still equals to cpos, I think.
1275 */
1276 utf8_encode_to_buf(cmdline.line);
1277 str = conv_buffer;
1278 }
1279
1280 /* COMMAND_MODE or SEARCH_MODE */
1281 w = u_str_width(str);
1282 ch = ':';
1283 if (input_mode == SEARCH_MODE)
1284 ch = search_direction == SEARCH_FORWARD ? '/' : '?';
1285
1286 if (w <= COLS - 2) {
1287 addch(ch);
1288 idx = u_copy_chars(print_buffer, str, &w);
1289 print_buffer[idx] = 0;
1290 dump_buffer(print_buffer);
1291 clrtoeol();
1292 } else {
1293 /* keep cursor as far right as possible */
1294 int skip, width, cw;
1295
1296 /* cursor pos (width, not chars. doesn't count the ':') */
1297 cw = u_str_nwidth(str, cmdline.cpos);
1298
1299 skip = cw + 2 - COLS;
1300 if (skip > 0) {
1301 /* skip the ':' */
1302 skip--;
1303
1304 /* skip rest (if any) */
1305 idx = u_skip_chars(str, &skip);
1306
1307 width = COLS;
1308 idx = u_copy_chars(print_buffer, str + idx, &width);
1309 while (width < COLS) {
1310 /* cursor is at end of the buffer
1311 * print 1, 2 or 3 spaces
1312 *
1313 * To clarify:
1314 *
1315 * If the last _skipped_ character was double-width we may need
1316 * to print 2 spaces.
1317 *
1318 * If the last _skipped_ character was invalid UTF-8 we may need
1319 * to print 3 spaces.
1320 */
1321 print_buffer[idx++] = ' ';
1322 width++;
1323 }
1324 print_buffer[idx] = 0;
1325 dump_buffer(print_buffer);
1326 } else {
1327 /* print ':' + COLS - 1 chars */
1328 addch(ch);
1329 width = COLS - 1;
1330 idx = u_copy_chars(print_buffer, str, &width);
1331 print_buffer[idx] = 0;
1332 dump_buffer(print_buffer);
1333 }
1334 }
1335 }
1336
set_title(const char * title)1337 static void set_title(const char *title)
1338 {
1339 if (!set_term_title)
1340 return;
1341
1342 if (t_ts) {
1343 printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
1344 fflush(stdout);
1345 }
1346 }
1347
do_update_titleline(void)1348 static void do_update_titleline(void)
1349 {
1350 bkgdset(pairs[CURSED_TITLELINE]);
1351 if (player_info.ti) {
1352 int i, use_alt_format = 0;
1353 char *wtitle;
1354
1355 fill_track_fopts_track_info(player_info.ti);
1356
1357 use_alt_format = !track_info_has_tag(player_info.ti);
1358
1359 if (is_http_url(player_info.ti->filename)) {
1360 const char *title = get_stream_title();
1361
1362 if (title != NULL) {
1363 free(title_buf);
1364 title_buf = to_utf8(title, icecast_default_charset);
1365 /*
1366 * StreamTitle overrides radio station name
1367 */
1368 use_alt_format = 0;
1369 fopt_set_str(&track_fopts[TF_TITLE], title_buf);
1370 }
1371 }
1372
1373 if (use_alt_format && *current_alt_format) {
1374 format_print(print_buffer, COLS, current_alt_format, track_fopts);
1375 } else {
1376 format_print(print_buffer, COLS, current_format, track_fopts);
1377 }
1378 dump_print_buffer(LINES - 3, 0);
1379
1380 /* set window title */
1381 if (use_alt_format && *window_title_alt_format) {
1382 format_print(print_buffer, print_buffer_max_width,
1383 window_title_alt_format, track_fopts);
1384 } else {
1385 format_print(print_buffer, print_buffer_max_width,
1386 window_title_format, track_fopts);
1387 }
1388
1389 /* remove whitespace */
1390 i = strlen(print_buffer) - 1;
1391 while (i > 0 && print_buffer[i] == ' ')
1392 i--;
1393 print_buffer[i + 1] = 0;
1394
1395 if (using_utf8) {
1396 wtitle = print_buffer;
1397 } else {
1398 utf8_decode(print_buffer);
1399 wtitle = conv_buffer;
1400 }
1401
1402 set_title(wtitle);
1403 } else {
1404 move(LINES - 3, 0);
1405 clrtoeol();
1406
1407 set_title("cmus " VERSION);
1408 }
1409 }
1410
cmdline_cursor_column(void)1411 static int cmdline_cursor_column(void)
1412 {
1413 char *str;
1414 int cw, skip, s;
1415
1416 str = cmdline.line;
1417 if (!using_utf8) {
1418 /* see do_update_commandline */
1419 utf8_encode_to_buf(cmdline.line);
1420 str = conv_buffer;
1421 }
1422
1423 /* width of the text in the buffer before cursor */
1424 cw = u_str_nwidth(str, cmdline.cpos);
1425
1426 if (1 + cw < COLS) {
1427 /* whole line is visible */
1428 return 1 + cw;
1429 }
1430
1431 /* beginning of cmdline is not visible */
1432
1433 /* check if the first visible char in cmdline would be halved
1434 * double-width character (or invalid byte <xx>) which is not possible.
1435 * we need to skip the whole character and move cursor to COLS - 2
1436 * column. */
1437 skip = cw + 2 - COLS;
1438
1439 /* skip the ':' */
1440 skip--;
1441
1442 /* skip rest */
1443 s = skip;
1444 u_skip_chars(str, &s);
1445 if (s > skip) {
1446 /* the last skipped char was double-width or <xx> */
1447 return COLS - 1 - (s - skip);
1448 }
1449 return COLS - 1;
1450 }
1451
post_update(void)1452 static void post_update(void)
1453 {
1454 /* refresh makes cursor visible at least for urxvt */
1455 if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
1456 move(LINES - 1, cmdline_cursor_column());
1457 refresh();
1458 curs_set(1);
1459 } else {
1460 if (cursor_x >= 0) {
1461 move(cursor_y, cursor_x);
1462 } else {
1463 move(LINES - 1, 0);
1464 }
1465 refresh();
1466
1467 /* visible cursor is useful for screen readers */
1468 if (show_cursor) {
1469 curs_set(1);
1470 } else {
1471 curs_set(0);
1472 }
1473 }
1474 }
1475
get_stream_title_locked(void)1476 static const char *get_stream_title_locked(void)
1477 {
1478 static char stream_title[255 * 16 + 1];
1479 char *ptr, *title;
1480
1481 ptr = strstr(player_metadata, "StreamTitle='");
1482 if (ptr == NULL)
1483 return NULL;
1484 ptr += 13;
1485 title = ptr;
1486 while (*ptr) {
1487 if (*ptr == '\'' && *(ptr + 1) == ';') {
1488 memcpy(stream_title, title, ptr - title);
1489 stream_title[ptr - title] = 0;
1490 return stream_title;
1491 }
1492 ptr++;
1493 }
1494 return NULL;
1495 }
1496
get_stream_title(void)1497 const char *get_stream_title(void)
1498 {
1499 player_metadata_lock();
1500 const char *rv = get_stream_title_locked();
1501 player_metadata_unlock();
1502 return rv;
1503 }
1504
update_titleline(void)1505 void update_titleline(void)
1506 {
1507 curs_set(0);
1508 do_update_titleline();
1509 post_update();
1510 }
1511
update_full(void)1512 void update_full(void)
1513 {
1514 if (!ui_initialized)
1515 return;
1516
1517 curs_set(0);
1518
1519 do_update_view(1);
1520 do_update_titleline();
1521 do_update_statusline();
1522 do_update_commandline();
1523
1524 post_update();
1525 }
1526
update_commandline(void)1527 static void update_commandline(void)
1528 {
1529 curs_set(0);
1530 do_update_commandline();
1531 post_update();
1532 }
1533
update_statusline(void)1534 void update_statusline(void)
1535 {
1536 if (!ui_initialized)
1537 return;
1538
1539 curs_set(0);
1540 do_update_statusline();
1541 post_update();
1542 }
1543
update_filterline(void)1544 void update_filterline(void)
1545 {
1546 if (cur_view != TREE_VIEW && cur_view != SORTED_VIEW)
1547 return;
1548 if (lib_live_filter) {
1549 char buf[512];
1550 int w;
1551 bkgdset(pairs[CURSED_STATUSLINE]);
1552 snprintf(buf, sizeof(buf), "filtered: %s", lib_live_filter);
1553 w = clamp(strlen(buf) + 2, COLS/4, COLS/2);
1554 sprint(LINES-4, COLS-w, buf, w);
1555 }
1556 }
1557
info_msg(const char * format,...)1558 void info_msg(const char *format, ...)
1559 {
1560 va_list ap;
1561
1562 va_start(ap, format);
1563 vsnprintf(error_buf, sizeof(error_buf), format, ap);
1564 va_end(ap);
1565
1566 if (client_fd != -1) {
1567 write_all(client_fd, error_buf, strlen(error_buf));
1568 write_all(client_fd, "\n", 1);
1569 }
1570
1571 msg_is_error = 0;
1572
1573 update_commandline();
1574 }
1575
error_msg(const char * format,...)1576 void error_msg(const char *format, ...)
1577 {
1578 va_list ap;
1579
1580 strcpy(error_buf, "Error: ");
1581 va_start(ap, format);
1582 vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
1583 va_end(ap);
1584
1585 d_print("%s\n", error_buf);
1586 if (client_fd != -1) {
1587 write_all(client_fd, error_buf, strlen(error_buf));
1588 write_all(client_fd, "\n", 1);
1589 }
1590
1591 msg_is_error = 1;
1592 error_count++;
1593
1594 if (ui_initialized) {
1595 error_time = time(NULL);
1596 update_commandline();
1597 } else {
1598 warn("%s\n", error_buf);
1599 error_buf[0] = 0;
1600 }
1601 }
1602
yes_no_query(const char * format,...)1603 enum ui_query_answer yes_no_query(const char *format, ...)
1604 {
1605 char buffer[512];
1606 va_list ap;
1607 int ret = 0;
1608
1609 va_start(ap, format);
1610 vsnprintf(buffer, sizeof(buffer), format, ap);
1611 va_end(ap);
1612
1613 move(LINES - 1, 0);
1614 bkgdset(pairs[CURSED_INFO]);
1615
1616 /* no need to convert buffer.
1617 * it is always encoded in the right charset (assuming filenames are
1618 * encoded in same charset as LC_CTYPE).
1619 */
1620
1621 addstr(buffer);
1622 clrtoeol();
1623 refresh();
1624
1625 while (1) {
1626 int ch = getch();
1627 if (ch == ERR || ch == 0) {
1628 if (!cmus_running) {
1629 ret = UI_QUERY_ANSWER_ERROR;
1630 break;
1631 }
1632 continue;
1633 }
1634
1635 if (ch == 'y') {
1636 ret = UI_QUERY_ANSWER_YES;
1637 break;
1638 } else {
1639 ret = UI_QUERY_ANSWER_NO;
1640 break;
1641 }
1642 }
1643 update_commandline();
1644 return ret;
1645 }
1646
search_not_found(void)1647 void search_not_found(void)
1648 {
1649 const char *what = "Track";
1650
1651 if (search_restricted) {
1652 switch (cur_view) {
1653 case TREE_VIEW:
1654 what = "Artist/album";
1655 break;
1656 case SORTED_VIEW:
1657 case PLAYLIST_VIEW:
1658 case QUEUE_VIEW:
1659 what = "Title";
1660 break;
1661 case BROWSER_VIEW:
1662 what = "File/Directory";
1663 break;
1664 case FILTERS_VIEW:
1665 what = "Filter";
1666 break;
1667 case HELP_VIEW:
1668 what = "Binding/command/option";
1669 break;
1670 }
1671 } else {
1672 switch (cur_view) {
1673 case TREE_VIEW:
1674 case SORTED_VIEW:
1675 case PLAYLIST_VIEW:
1676 case QUEUE_VIEW:
1677 what = "Track";
1678 break;
1679 case BROWSER_VIEW:
1680 what = "File/Directory";
1681 break;
1682 case FILTERS_VIEW:
1683 what = "Filter";
1684 break;
1685 case HELP_VIEW:
1686 what = "Binding/command/option";
1687 break;
1688 }
1689 }
1690 info_msg("%s not found: %s", what, search_str ? search_str : "");
1691 }
1692
set_client_fd(int fd)1693 void set_client_fd(int fd)
1694 {
1695 client_fd = fd;
1696 }
1697
get_client_fd(void)1698 int get_client_fd(void)
1699 {
1700 return client_fd;
1701 }
1702
set_view(int view)1703 void set_view(int view)
1704 {
1705 if (view == cur_view)
1706 return;
1707
1708 prev_view = cur_view;
1709 cur_view = view;
1710 switch (cur_view) {
1711 case TREE_VIEW:
1712 searchable = tree_searchable;
1713 break;
1714 case SORTED_VIEW:
1715 searchable = lib_editable.shared->searchable;
1716 break;
1717 case PLAYLIST_VIEW:
1718 searchable = pl_get_searchable();
1719 break;
1720 case QUEUE_VIEW:
1721 searchable = pq_editable.shared->searchable;
1722 break;
1723 case BROWSER_VIEW:
1724 searchable = browser_searchable;
1725 break;
1726 case FILTERS_VIEW:
1727 searchable = filters_searchable;
1728 break;
1729 case HELP_VIEW:
1730 searchable = help_searchable;
1731 update_help_window();
1732 break;
1733 }
1734
1735 curs_set(0);
1736 do_update_view(1);
1737 post_update();
1738 }
1739
enter_command_mode(void)1740 void enter_command_mode(void)
1741 {
1742 error_buf[0] = 0;
1743 error_time = 0;
1744 input_mode = COMMAND_MODE;
1745 update_commandline();
1746 }
1747
enter_search_mode(void)1748 void enter_search_mode(void)
1749 {
1750 error_buf[0] = 0;
1751 error_time = 0;
1752 input_mode = SEARCH_MODE;
1753 search_direction = SEARCH_FORWARD;
1754 update_commandline();
1755 }
1756
enter_search_backward_mode(void)1757 void enter_search_backward_mode(void)
1758 {
1759 error_buf[0] = 0;
1760 error_time = 0;
1761 input_mode = SEARCH_MODE;
1762 search_direction = SEARCH_BACKWARD;
1763 update_commandline();
1764 }
1765
update_colors(void)1766 void update_colors(void)
1767 {
1768 int i;
1769
1770 if (!ui_initialized)
1771 return;
1772
1773 for (i = 0; i < NR_CURSED; i++) {
1774 int bg = colors[cursed_to_bg_idx[i]];
1775 int fg = colors[cursed_to_fg_idx[i]];
1776 int attr = attrs[cursed_to_attr_idx[i]];
1777 int pair = i + 1;
1778
1779 if (fg >= 8 && fg <= 15) {
1780 /* fg colors 8..15 are special (0..7 + bold) */
1781 init_pair(pair, fg & 7, bg);
1782 pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0) | attr;
1783 } else {
1784 init_pair(pair, fg, bg);
1785 pairs[i] = COLOR_PAIR(pair) | attr;
1786 }
1787 }
1788 }
1789
clear_error(void)1790 static void clear_error(void)
1791 {
1792 time_t t = time(NULL);
1793
1794 /* prevent accidental clearing of error messages */
1795 if (t - error_time < 2)
1796 return;
1797
1798 if (error_buf[0]) {
1799 error_time = 0;
1800 error_buf[0] = 0;
1801 update_commandline();
1802 }
1803 }
1804
1805 /* screen updates }}} */
1806
fill_status_program_track_info_args(char ** argv,int i,struct track_info * ti)1807 static int fill_status_program_track_info_args(char **argv, int i, struct track_info *ti)
1808 {
1809 /* returns first free argument index */
1810
1811 const char *stream_title = NULL;
1812 if (player_info.status == PLAYER_STATUS_PLAYING && is_http_url(ti->filename))
1813 stream_title = get_stream_title();
1814
1815 static const char *keys[] = {
1816 "artist", "albumartist", "album", "discnumber", "tracknumber", "title",
1817 "date", "musicbrainz_trackid", NULL
1818 };
1819 int j;
1820
1821 if (is_http_url(ti->filename)) {
1822 argv[i++] = xstrdup("url");
1823 } else {
1824 argv[i++] = xstrdup("file");
1825 }
1826 argv[i++] = xstrdup(ti->filename);
1827
1828 if (track_info_has_tag(ti)) {
1829 for (j = 0; keys[j]; j++) {
1830 const char *key = keys[j];
1831 const char *val;
1832
1833 if (strcmp(key, "title") == 0 && stream_title)
1834 /*
1835 * StreamTitle overrides radio station name
1836 */
1837 val = stream_title;
1838 else
1839 val = keyvals_get_val(ti->comments, key);
1840
1841 if (val) {
1842 argv[i++] = xstrdup(key);
1843 argv[i++] = xstrdup(val);
1844 }
1845 }
1846 if (ti->duration > 0) {
1847 char buf[32];
1848 snprintf(buf, sizeof(buf), "%d", ti->duration);
1849 argv[i++] = xstrdup("duration");
1850 argv[i++] = xstrdup(buf);
1851 }
1852 } else if (stream_title) {
1853 argv[i++] = xstrdup("title");
1854 argv[i++] = xstrdup(stream_title);
1855 }
1856
1857 return i;
1858 }
1859
spawn_status_program_inner(const char * status_text,struct track_info * ti)1860 static void spawn_status_program_inner(const char *status_text, struct track_info *ti)
1861 {
1862 if (status_display_program == NULL || status_display_program[0] == 0)
1863 return;
1864
1865 char *argv[32];
1866 int i = 0;
1867
1868 argv[i++] = xstrdup(status_display_program);
1869
1870 argv[i++] = xstrdup("status");
1871 argv[i++] = xstrdup(status_text);
1872
1873 if (ti) {
1874 i = fill_status_program_track_info_args(argv, i, ti);
1875 }
1876 argv[i++] = NULL;
1877
1878 if (spawn(argv, NULL, 0) == -1)
1879 error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
1880 for (i = 0; argv[i]; i++)
1881 free(argv[i]);
1882 }
1883
spawn_status_program(void)1884 static void spawn_status_program(void)
1885 {
1886 spawn_status_program_inner(player_status_names[player_info.status], player_info.ti);
1887 }
1888
1889 static volatile sig_atomic_t ctrl_c_pressed = 0;
1890
sig_int(int sig)1891 static void sig_int(int sig)
1892 {
1893 ctrl_c_pressed = 1;
1894 }
1895
sig_shutdown(int sig)1896 static void sig_shutdown(int sig)
1897 {
1898 d_print("sig_shutdown %d\n", sig);
1899 cmus_running = 0;
1900 }
1901
1902 static volatile sig_atomic_t needs_to_resize = 1;
1903
sig_winch(int sig)1904 static void sig_winch(int sig)
1905 {
1906 needs_to_resize = 1;
1907 }
1908
update_size(void)1909 void update_size(void) {
1910 needs_to_resize = 1;
1911 }
1912
get_window_size(int * lines,int * columns)1913 static int get_window_size(int *lines, int *columns)
1914 {
1915 struct winsize ws;
1916
1917 if (ioctl(0, TIOCGWINSZ, &ws) == -1)
1918 return -1;
1919 *columns = ws.ws_col;
1920 *lines = ws.ws_row;
1921 return 0;
1922 }
1923
resize_tree_view(int w,int h)1924 static void resize_tree_view(int w, int h)
1925 {
1926 tree_win_w = w * ((float)tree_width_percent / 100.0f);
1927 if (tree_width_max && tree_win_w > tree_width_max)
1928 tree_win_w = tree_width_max;
1929 track_win_w = w - tree_win_w - 1;
1930 if (tree_win_w < 8)
1931 tree_win_w = 8;
1932 if (track_win_w < 8)
1933 track_win_w = 8;
1934 tree_win_x = 0;
1935 track_win_x = tree_win_w + 1;
1936
1937 h--;
1938 window_set_nr_rows(lib_tree_win, h);
1939 window_set_nr_rows(lib_track_win, h);
1940 }
1941
update(void)1942 static void update(void)
1943 {
1944 int needs_view_update = 0;
1945 int needs_title_update = 0;
1946 int needs_status_update = 0;
1947 int needs_command_update = 0;
1948 int needs_spawn = 0;
1949
1950 if (needs_to_resize) {
1951 int w, h;
1952 int columns, lines;
1953
1954 if (get_window_size(&lines, &columns) == 0) {
1955 needs_to_resize = 0;
1956 #if HAVE_RESIZETERM
1957 resizeterm(lines, columns);
1958 #endif
1959 editable_win_w = COLS;
1960 w = COLS;
1961 h = LINES - 3;
1962 if (w < 16)
1963 w = 16;
1964 if (h < 2)
1965 h = 2;
1966 resize_tree_view(w, h);
1967 window_set_nr_rows(lib_editable.shared->win, h - 1);
1968 pl_set_nr_rows(h - 1);
1969 window_set_nr_rows(pq_editable.shared->win, h - 1);
1970 window_set_nr_rows(filters_win, h - 1);
1971 window_set_nr_rows(help_win, h - 1);
1972 window_set_nr_rows(browser_win, h - 1);
1973 needs_title_update = 1;
1974 needs_status_update = 1;
1975 needs_command_update = 1;
1976 }
1977 clearok(curscr, TRUE);
1978 refresh();
1979 }
1980
1981 if (player_info.status_changed)
1982 mpris_playback_status_changed();
1983
1984 if (player_info.file_changed || player_info.metadata_changed)
1985 mpris_metadata_changed();
1986
1987 needs_spawn = player_info.status_changed || player_info.file_changed ||
1988 player_info.metadata_changed;
1989
1990 if (player_info.file_changed) {
1991 needs_title_update = 1;
1992 needs_status_update = 1;
1993 }
1994 if (player_info.metadata_changed)
1995 needs_title_update = 1;
1996 if (player_info.position_changed || player_info.status_changed)
1997 needs_status_update = 1;
1998 switch (cur_view) {
1999 case TREE_VIEW:
2000 needs_view_update += lib_tree_win->changed || lib_track_win->changed;
2001 break;
2002 case SORTED_VIEW:
2003 needs_view_update += lib_editable.shared->win->changed;
2004 break;
2005 case PLAYLIST_VIEW:
2006 needs_view_update += pl_needs_redraw();
2007 break;
2008 case QUEUE_VIEW:
2009 needs_view_update += pq_editable.shared->win->changed;
2010 break;
2011 case BROWSER_VIEW:
2012 needs_view_update += browser_win->changed;
2013 break;
2014 case FILTERS_VIEW:
2015 needs_view_update += filters_win->changed;
2016 break;
2017 case HELP_VIEW:
2018 needs_view_update += help_win->changed;
2019 break;
2020 }
2021
2022 /* total time changed? */
2023 if (play_library) {
2024 needs_status_update += lib_editable.shared->win->changed;
2025 lib_editable.shared->win->changed = 0;
2026 } else {
2027 needs_status_update += pl_needs_redraw();
2028 }
2029
2030 if (needs_spawn)
2031 spawn_status_program();
2032
2033 if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
2034 curs_set(0);
2035
2036 if (needs_view_update)
2037 do_update_view(0);
2038 if (needs_title_update)
2039 do_update_titleline();
2040 if (needs_status_update)
2041 do_update_statusline();
2042 if (needs_command_update)
2043 do_update_commandline();
2044 post_update();
2045 }
2046 }
2047
handle_ch(uchar ch)2048 static void handle_ch(uchar ch)
2049 {
2050 clear_error();
2051 if (input_mode == NORMAL_MODE) {
2052 normal_mode_ch(ch);
2053 } else if (input_mode == COMMAND_MODE) {
2054 command_mode_ch(ch);
2055 update_commandline();
2056 } else if (input_mode == SEARCH_MODE) {
2057 search_mode_ch(ch);
2058 update_commandline();
2059 }
2060 }
2061
handle_escape(int c)2062 static void handle_escape(int c)
2063 {
2064 clear_error();
2065 if (input_mode == NORMAL_MODE) {
2066 normal_mode_ch(c + 128);
2067 } else if (input_mode == COMMAND_MODE) {
2068 command_mode_escape(c);
2069 update_commandline();
2070 } else if (input_mode == SEARCH_MODE) {
2071 search_mode_escape(c);
2072 update_commandline();
2073 }
2074 }
2075
handle_key(int key)2076 static void handle_key(int key)
2077 {
2078 clear_error();
2079 if (input_mode == NORMAL_MODE) {
2080 normal_mode_key(key);
2081 } else if (input_mode == COMMAND_MODE) {
2082 command_mode_key(key);
2083 update_commandline();
2084 } else if (input_mode == SEARCH_MODE) {
2085 search_mode_key(key);
2086 update_commandline();
2087 }
2088 }
2089
handle_mouse(MEVENT * event)2090 static void handle_mouse(MEVENT *event)
2091 {
2092 #if NCURSES_MOUSE_VERSION <= 1
2093 static int last_mevent;
2094
2095 if ((last_mevent & BUTTON1_PRESSED) && (event->bstate & REPORT_MOUSE_POSITION))
2096 event->bstate = BUTTON1_RELEASED;
2097 last_mevent = event->bstate;
2098 #endif
2099
2100 clear_error();
2101 if (input_mode == NORMAL_MODE) {
2102 normal_mode_mouse(event);
2103 } else if (input_mode == COMMAND_MODE) {
2104 command_mode_mouse(event);
2105 update_commandline();
2106 } else if (input_mode == SEARCH_MODE) {
2107 search_mode_mouse(event);
2108 update_commandline();
2109 }
2110 }
2111
u_getch(void)2112 static void u_getch(void)
2113 {
2114 int key;
2115 int bit = 7;
2116 int mask = (1 << 7);
2117 uchar u, ch;
2118
2119 key = getch();
2120 if (key == ERR || key == 0)
2121 return;
2122
2123 if (key == KEY_MOUSE) {
2124 MEVENT event;
2125 if (getmouse(&event) == OK)
2126 handle_mouse(&event);
2127 return;
2128 }
2129
2130 if (key > 255) {
2131 handle_key(key);
2132 return;
2133 }
2134
2135 /* escape sequence */
2136 if (key == 0x1B) {
2137 cbreak();
2138 int e_key = getch();
2139 halfdelay(5);
2140 if (e_key != ERR && e_key != 0) {
2141 handle_escape(e_key);
2142 return;
2143 }
2144 }
2145
2146 ch = (unsigned char)key;
2147 while (bit > 0 && ch & mask) {
2148 mask >>= 1;
2149 bit--;
2150 }
2151 if (bit == 7) {
2152 /* ascii */
2153 u = ch;
2154 } else if (using_utf8) {
2155 int count;
2156
2157 u = ch & ((1 << bit) - 1);
2158 count = 6 - bit;
2159 while (count) {
2160 key = getch();
2161 if (key == ERR || key == 0)
2162 return;
2163
2164 ch = (unsigned char)key;
2165 u = (u << 6) | (ch & 63);
2166 count--;
2167 }
2168 } else
2169 u = ch | U_INVALID_MASK;
2170 handle_ch(u);
2171 }
2172
main_loop(void)2173 static void main_loop(void)
2174 {
2175 int rc, fd_high;
2176
2177 #define SELECT_ADD_FD(fd) do {\
2178 FD_SET((fd), &set); \
2179 if ((fd) > fd_high) \
2180 fd_high = (fd); \
2181 } while(0)
2182
2183 fd_high = server_socket;
2184 while (cmus_running) {
2185 fd_set set;
2186 struct timeval tv;
2187 int poll_mixer = 0;
2188 int i, nr_fds = 0;
2189 int fds[NR_MIXER_FDS];
2190 struct list_head *item;
2191 struct client *client;
2192
2193 player_info_snapshot();
2194
2195 update();
2196
2197 /* Timeout must be so small that screen updates seem instant.
2198 * Only affects changes done in other threads (player).
2199 *
2200 * Too small timeout makes window updates too fast (wastes CPU).
2201 *
2202 * Too large timeout makes status line (position) updates too slow.
2203 * The timeout is accuracy of player position.
2204 */
2205 tv.tv_sec = 0;
2206 tv.tv_usec = 0;
2207
2208 if (player_info.status == PLAYER_STATUS_PLAYING) {
2209 // player position updates need to be fast
2210 tv.tv_usec = 100e3;
2211 }
2212
2213 FD_ZERO(&set);
2214 SELECT_ADD_FD(0);
2215 SELECT_ADD_FD(job_fd);
2216 SELECT_ADD_FD(cmus_next_track_request_fd);
2217 SELECT_ADD_FD(server_socket);
2218 if (mpris_fd != -1)
2219 SELECT_ADD_FD(mpris_fd);
2220 list_for_each_entry(client, &client_head, node) {
2221 SELECT_ADD_FD(client->fd);
2222 }
2223 if (!soft_vol) {
2224 nr_fds = mixer_get_fds(fds);
2225 if (nr_fds <= 0) {
2226 poll_mixer = 1;
2227 if (!tv.tv_usec)
2228 tv.tv_usec = 500e3;
2229 }
2230 for (i = 0; i < nr_fds; i++) {
2231 BUG_ON(fds[i] <= 0);
2232 SELECT_ADD_FD(fds[i]);
2233 }
2234 }
2235
2236 rc = select(fd_high + 1, &set, NULL, NULL, tv.tv_usec ? &tv : NULL);
2237 if (poll_mixer) {
2238 int ol = volume_l;
2239 int or = volume_r;
2240
2241 mixer_read_volume();
2242 if (ol != volume_l || or != volume_r) {
2243 mpris_volume_changed();
2244 update_statusline();
2245 }
2246
2247 }
2248 if (rc <= 0) {
2249 if (ctrl_c_pressed) {
2250 handle_ch(0x03);
2251 ctrl_c_pressed = 0;
2252 }
2253
2254 continue;
2255 }
2256
2257 for (i = 0; i < nr_fds; i++) {
2258 if (FD_ISSET(fds[i], &set)) {
2259 d_print("vol changed\n");
2260 mixer_read_volume();
2261 mpris_volume_changed();
2262 update_statusline();
2263 }
2264 }
2265 if (FD_ISSET(server_socket, &set))
2266 server_accept();
2267
2268 // server_serve() can remove client from the list
2269 item = client_head.next;
2270 while (item != &client_head) {
2271 struct list_head *next = item->next;
2272 client = container_of(item, struct client, node);
2273 if (FD_ISSET(client->fd, &set))
2274 server_serve(client);
2275 item = next;
2276 }
2277
2278 if (FD_ISSET(0, &set))
2279 u_getch();
2280
2281 if (mpris_fd != -1 && FD_ISSET(mpris_fd, &set))
2282 mpris_process();
2283
2284 if (FD_ISSET(job_fd, &set))
2285 job_handle();
2286
2287 if (FD_ISSET(cmus_next_track_request_fd, &set))
2288 cmus_provide_next_track();
2289 }
2290 }
2291
init_curses(void)2292 static void init_curses(void)
2293 {
2294 struct sigaction act;
2295 char *ptr, *term;
2296
2297 sigemptyset(&act.sa_mask);
2298 act.sa_flags = 0;
2299 act.sa_handler = sig_int;
2300 sigaction(SIGINT, &act, NULL);
2301
2302 sigemptyset(&act.sa_mask);
2303 act.sa_flags = 0;
2304 act.sa_handler = sig_shutdown;
2305 sigaction(SIGHUP, &act, NULL);
2306 sigaction(SIGTERM, &act, NULL);
2307
2308 sigemptyset(&act.sa_mask);
2309 act.sa_flags = 0;
2310 act.sa_handler = SIG_IGN;
2311 sigaction(SIGPIPE, &act, NULL);
2312
2313 sigemptyset(&act.sa_mask);
2314 act.sa_flags = 0;
2315 act.sa_handler = sig_winch;
2316 sigaction(SIGWINCH, &act, NULL);
2317
2318 initscr();
2319 nodelay(stdscr, TRUE);
2320 keypad(stdscr, TRUE);
2321 halfdelay(5);
2322 noecho();
2323
2324 if (has_colors()) {
2325 #if HAVE_USE_DEFAULT_COLORS
2326 start_color();
2327 use_default_colors();
2328 #endif
2329 }
2330 d_print("Number of supported colors: %d\n", COLORS);
2331 ui_initialized = 1;
2332
2333 /* this was disabled while initializing because it needs to be
2334 * called only once after all colors have been set
2335 */
2336 update_colors();
2337
2338 ptr = tcap_buffer;
2339 t_ts = tgetstr("ts", &ptr);
2340 t_fs = tgetstr("fs", &ptr);
2341 d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
2342
2343 if (!t_fs)
2344 t_ts = NULL;
2345
2346 term = getenv("TERM");
2347 if (!t_ts && term) {
2348 /*
2349 * Eterm: Eterm
2350 * aterm: rxvt
2351 * mlterm: xterm
2352 * terminal (xfce): xterm
2353 * urxvt: rxvt-unicode
2354 * xterm: xterm, xterm-{,16,88,256}color
2355 */
2356 if (!strcmp(term, "screen")) {
2357 t_ts = "\033_";
2358 t_fs = "\033\\";
2359 } else if (!strncmp(term, "xterm", 5) ||
2360 !strncmp(term, "rxvt", 4) ||
2361 !strcmp(term, "Eterm")) {
2362 /* \033]1; change icon
2363 * \033]2; change title
2364 * \033]0; change both
2365 */
2366 t_ts = "\033]0;";
2367 t_fs = "\007";
2368 }
2369 }
2370 update_mouse();
2371
2372 if (!getenv("ESCDELAY")) {
2373 set_escdelay(default_esc_delay);
2374 }
2375 }
2376
init_all(void)2377 static void init_all(void)
2378 {
2379 main_thread = pthread_self();
2380 cmus_track_request_init();
2381
2382 server_init(server_address);
2383
2384 /* does not select output plugin */
2385 player_init();
2386
2387 /* plugins have been loaded so we know what plugin options are available */
2388 options_add();
2389
2390 lib_init();
2391 searchable = tree_searchable;
2392 cmus_init();
2393 pl_init();
2394 browser_init();
2395 filters_init();
2396 help_init();
2397 cmdline_init();
2398 commands_init();
2399 search_mode_init();
2400
2401 /* almost everything must be initialized now */
2402 options_load();
2403 if (mpris)
2404 mpris_init();
2405
2406 /* finally we can set the output plugin */
2407 player_set_op(output_plugin);
2408 if (!soft_vol)
2409 mixer_open();
2410
2411 lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
2412 play_queue_autosave_filename = xstrjoin(cmus_config_dir, "/queue.pl");
2413 lib_filename = xstrdup(lib_autosave_filename);
2414
2415 if (error_count) {
2416 char buf[16];
2417 char *ret;
2418
2419 warn("Press <enter> to continue.");
2420
2421 ret = fgets(buf, sizeof(buf), stdin);
2422 BUG_ON(ret == NULL);
2423 }
2424 help_add_all_unbound();
2425
2426 init_curses();
2427
2428 if (resume_cmus) {
2429 resume_load();
2430 cmus_add(play_queue_append, play_queue_autosave_filename,
2431 FILE_TYPE_PL, JOB_TYPE_QUEUE, 0, NULL);
2432 } else {
2433 set_view(start_view);
2434 }
2435
2436 cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL,
2437 JOB_TYPE_LIB, 0, NULL);
2438
2439 worker_start();
2440 }
2441
exit_all(void)2442 static void exit_all(void)
2443 {
2444 endwin();
2445
2446 if (resume_cmus)
2447 resume_exit();
2448 options_exit();
2449
2450 server_exit();
2451 cmus_exit();
2452 if (resume_cmus)
2453 cmus_save(play_queue_for_each, play_queue_autosave_filename,
2454 NULL);
2455 cmus_save(lib_for_each, lib_autosave_filename, NULL);
2456
2457 pl_exit();
2458 player_exit();
2459 op_exit_plugins();
2460 commands_exit();
2461 search_mode_exit();
2462 filters_exit();
2463 help_exit();
2464 browser_exit();
2465 mpris_free();
2466 }
2467
2468 enum {
2469 FLAG_LISTEN,
2470 FLAG_PLUGINS,
2471 FLAG_SHOW_CURSOR,
2472 FLAG_HELP,
2473 FLAG_VERSION,
2474 NR_FLAGS
2475 };
2476
2477 static struct option options[NR_FLAGS + 1] = {
2478 { 0, "listen", 1 },
2479 { 0, "plugins", 0 },
2480 { 0, "show-cursor", 0 },
2481 { 0, "help", 0 },
2482 { 0, "version", 0 },
2483 { 0, NULL, 0 }
2484 };
2485
2486 static const char *usage =
2487 "Usage: %s [OPTION]...\n"
2488 "Curses based music player.\n"
2489 "\n"
2490 " --listen ADDR listen on ADDR instead of $CMUS_SOCKET or $XDG_RUNTIME_DIR/cmus-socket\n"
2491 " ADDR is either a UNIX socket or host[:port]\n"
2492 " WARNING: using TCP/IP is insecure!\n"
2493 " --plugins list available plugins and exit\n"
2494 " --show-cursor always visible cursor\n"
2495 " --help display this help and exit\n"
2496 " --version " VERSION "\n"
2497 "\n"
2498 "Use cmus-remote to control cmus from command line.\n"
2499 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
2500
main(int argc,char * argv[])2501 int main(int argc, char *argv[])
2502 {
2503 int list_plugins = 0;
2504
2505 program_name = argv[0];
2506 argv++;
2507 while (1) {
2508 int idx;
2509 char *arg;
2510
2511 idx = get_option(&argv, options, &arg);
2512 if (idx < 0)
2513 break;
2514
2515 switch (idx) {
2516 case FLAG_HELP:
2517 printf(usage, program_name);
2518 return 0;
2519 case FLAG_VERSION:
2520 printf("cmus " VERSION
2521 "\nCopyright 2004-2006 Timo Hirvonen"
2522 "\nCopyright 2008-2016 Various Authors\n");
2523 return 0;
2524 case FLAG_PLUGINS:
2525 list_plugins = 1;
2526 break;
2527 case FLAG_LISTEN:
2528 server_address = xstrdup(arg);
2529 break;
2530 case FLAG_SHOW_CURSOR:
2531 show_cursor = 1;
2532 break;
2533 }
2534 }
2535
2536 setlocale(LC_CTYPE, "");
2537 setlocale(LC_COLLATE, "");
2538 charset = getenv("CMUS_CHARSET");
2539 if (!charset || !charset[0]) {
2540 #ifdef CODESET
2541 charset = nl_langinfo(CODESET);
2542 #else
2543 charset = "ISO-8859-1";
2544 #endif
2545 }
2546 if (strcmp(charset, "UTF-8") == 0)
2547 using_utf8 = 1;
2548
2549 misc_init();
2550 if (server_address == NULL)
2551 server_address = xstrdup(cmus_socket_path);
2552 debug_init();
2553 d_print("charset = '%s'\n", charset);
2554
2555 ip_load_plugins();
2556 op_load_plugins();
2557 if (list_plugins) {
2558 ip_dump_plugins();
2559 op_dump_plugins();
2560 return 0;
2561 }
2562 init_all();
2563 main_loop();
2564 exit_all();
2565 spawn_status_program_inner("exiting", NULL);
2566 return 0;
2567 }
2568