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