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 "command_mode.h"
20 #include "search_mode.h"
21 #include "cmdline.h"
22 #include "options.h"
23 #include "ui_curses.h"
24 #include "history.h"
25 #include "tabexp.h"
26 #include "tabexp_file.h"
27 #include "browser.h"
28 #include "filters.h"
29 #include "player.h"
30 #include "output.h"
31 #include "editable.h"
32 #include "lib.h"
33 #include "pl.h"
34 #include "play_queue.h"
35 #include "cmus.h"
36 #include "worker.h"
37 #include "keys.h"
38 #include "xmalloc.h"
39 #include "xstrjoin.h"
40 #include "misc.h"
41 #include "path.h"
42 #include "spawn.h"
43 #include "utils.h"
44 #include "list.h"
45 #include "debug.h"
46 #include "load_dir.h"
47 #include "help.h"
48 #include "op.h"
49 #include "mpris.h"
50 #include "job.h"
51 
52 #include <stdlib.h>
53 #include <ctype.h>
54 #include <sys/types.h>
55 #include <sys/wait.h>
56 #include <dirent.h>
57 
58 static struct history cmd_history;
59 static char *cmd_history_filename;
60 static char *history_search_text = NULL;
61 static int arg_expand_cmd = -1;
62 static int mute_vol_l = 0, mute_vol_r = 0;
63 
64 /* view {{{ */
65 
view_clear(int view)66 void view_clear(int view)
67 {
68 	switch (view) {
69 	case TREE_VIEW:
70 	case SORTED_VIEW:
71 		worker_remove_jobs_by_type(JOB_TYPE_LIB);
72 		editable_clear(&lib_editable);
73 
74 		/* FIXME: make this optional? */
75 		lib_clear_store();
76 		break;
77 	case PLAYLIST_VIEW:
78 		pl_clear();
79 		break;
80 	case QUEUE_VIEW:
81 		worker_remove_jobs_by_type(JOB_TYPE_QUEUE);
82 		editable_clear(&pq_editable);
83 		break;
84 	default:
85 		info_msg(":clear only works in views 1-4");
86 	}
87 }
88 
view_add(int view,char * arg,int prepend)89 void view_add(int view, char *arg, int prepend)
90 {
91 	char *tmp, *name;
92 	enum file_type ft;
93 
94 	tmp = expand_filename(arg);
95 	ft = cmus_detect_ft(tmp, &name);
96 	if (ft == FILE_TYPE_INVALID) {
97 		error_msg("adding '%s': %s", tmp, strerror(errno));
98 		free(tmp);
99 		return;
100 	}
101 	free(tmp);
102 
103 	switch (view) {
104 	case TREE_VIEW:
105 	case SORTED_VIEW:
106 		cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB, 0, NULL);
107 		break;
108 	case PLAYLIST_VIEW:
109 		pl_add_file_to_marked_pl(name);
110 		break;
111 	case QUEUE_VIEW:
112 		if (prepend) {
113 			cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE,
114 					0, NULL);
115 		} else {
116 			cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE, 0,
117 					NULL);
118 		}
119 		break;
120 	default:
121 		info_msg(":add only works in views 1-4");
122 	}
123 	free(name);
124 }
125 
view_load_prepare(char * arg)126 static char *view_load_prepare(char *arg)
127 {
128 	char *name, *tmp = expand_filename(arg);
129 	enum file_type ft = cmus_detect_ft(tmp, &name);
130 	if (ft == FILE_TYPE_INVALID) {
131 		error_msg("loading '%s': %s", tmp, strerror(errno));
132 		free(tmp);
133 		return NULL;
134 	}
135 	free(tmp);
136 
137 	if (ft == FILE_TYPE_FILE)
138 		ft = FILE_TYPE_PL;
139 	if (ft != FILE_TYPE_PL) {
140 		error_msg("loading '%s': not a playlist file", name);
141 		free(name);
142 		return NULL;
143 	}
144 	return name;
145 }
146 
view_load(int view,char * arg)147 void view_load(int view, char *arg)
148 {
149 	char *name = view_load_prepare(arg);
150 	if (!name)
151 		return;
152 
153 	switch (view) {
154 	case TREE_VIEW:
155 	case SORTED_VIEW:
156 		worker_remove_jobs_by_type(JOB_TYPE_LIB);
157 		editable_clear(&lib_editable);
158 		cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB, 0,
159 				NULL);
160 		free(lib_filename);
161 		lib_filename = name;
162 		break;
163 	default:
164 		info_msg(":load only works in views 1-2");
165 		free(name);
166 	}
167 }
168 
do_save(for_each_ti_cb for_each_ti,const char * arg,char ** filenamep,save_ti_cb save_ti)169 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep,
170 		save_ti_cb save_ti)
171 {
172 	char *filename = *filenamep;
173 
174 	if (arg) {
175 		if (strcmp(arg, "-") == 0) {
176 			filename = (char *) arg;
177 		} else {
178 			free(filename);
179 			filename = xstrdup(arg);
180 			*filenamep = filename;
181 		}
182 	} else if (!filename) {
183 		error_msg("need a file as argument, no default stored yet");
184 		return;
185 	}
186 
187 	if (save_ti(for_each_ti, filename, NULL) == -1)
188 		error_msg("saving '%s': %s", filename, strerror(errno));
189 }
190 
view_save(int view,char * arg,int to_stdout,int filtered,int extended)191 void view_save(int view, char *arg, int to_stdout, int filtered, int extended)
192 {
193 	char **dest;
194 	save_ti_cb     save_ti         = extended ? cmus_save_ext         : cmus_save;
195 	for_each_ti_cb lib_for_each_ti = filtered ? lib_for_each_filtered : lib_for_each;
196 
197 	if (arg) {
198 		if (to_stdout) {
199 			arg = xstrdup(arg);
200 		} else {
201 			char *tmp = expand_filename(arg);
202 			arg = path_absolute(tmp);
203 			free(tmp);
204 		}
205 	}
206 
207 	switch (view) {
208 	case TREE_VIEW:
209 	case SORTED_VIEW:
210 		if (worker_has_job_by_type(JOB_TYPE_LIB))
211 			goto worker_running;
212 		dest = extended ? &lib_ext_filename : &lib_filename;
213 		do_save(lib_for_each_ti, arg, dest, save_ti);
214 		break;
215 	case PLAYLIST_VIEW:
216 		if (arg)
217 			pl_export_selected_pl(arg);
218 		else
219 			pl_save();
220 		break;
221 	case QUEUE_VIEW:
222 		if (worker_has_job_by_type(JOB_TYPE_QUEUE))
223 			goto worker_running;
224 		dest = extended ? &play_queue_ext_filename : &play_queue_filename;
225 		do_save(play_queue_for_each, arg, dest, save_ti);
226 		break;
227 	default:
228 		info_msg(":save only works in views 1 - 4");
229 	}
230 	free(arg);
231 	return;
232 worker_running:
233 	error_msg("can't save when tracks are being added");
234 	free(arg);
235 }
236 
237 /* }}} */
238 
239 /* if only_last != 0, only return the last flag */
do_parse_flags(const char ** strp,const char * flags,int only_last)240 static int do_parse_flags(const char **strp, const char *flags, int only_last)
241 {
242 	const char *str = *strp;
243 	int flag = 0;
244 
245 	if (str == NULL)
246 		return flag;
247 
248 	while (*str && (only_last || !flag)) {
249 		if (*str != '-')
250 			break;
251 
252 		// "-"
253 		if (str[1] == 0)
254 			break;
255 
256 		// "--" or "-- "
257 		if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
258 			str += 2;
259 			break;
260 		}
261 
262 		// not "-?" or "-? "
263 		if (str[2] && str[2] != ' ')
264 			break;
265 
266 		flag = str[1];
267 		if (!strchr(flags, flag)) {
268 			error_msg("invalid option -%c", flag);
269 			return -1;
270 		}
271 
272 		str += 2;
273 
274 		while (*str == ' ')
275 			str++;
276 	}
277 	while (*str == ' ')
278 		str++;
279 	if (*str == 0)
280 		str = NULL;
281 	*strp = str;
282 	return flag;
283 }
284 
parse_flags(const char ** strp,const char * flags)285 static int parse_flags(const char **strp, const char *flags)
286 {
287 	return do_parse_flags(strp, flags, 1);
288 }
289 
parse_one_flag(const char ** strp,const char * flags)290 static int parse_one_flag(const char **strp, const char *flags)
291 {
292 	return do_parse_flags(strp, flags, 0);
293 }
294 
295 /* is str == "...-", but not "...-- -" ? copied from do_parse_flags() */
is_stdout_filename(const char * str)296 static int is_stdout_filename(const char *str)
297 {
298 	if (!str)
299 		return 0;
300 
301 	while (*str) {
302 		if (*str != '-')
303 			return 0;
304 		// "-"
305 		if (str[1] == 0)
306 			return 1;
307 		// "--" or "-- "
308 		if (str[1] == '-' && (str[2] == 0 || str[2] == ' '))
309 			return 0;
310 		// not "-?" or "-? "
311 		if (str[2] && str[2] != ' ')
312 			return 0;
313 		str += 2;
314 		while (*str == ' ')
315 			str++;
316 	}
317 
318 	return 0;
319 }
320 
flag_to_view(int flag)321 static int flag_to_view(int flag)
322 {
323 	switch (flag) {
324 	case 'l':
325 	case 'L':
326 		return TREE_VIEW;
327 	case 'p':
328 		return PLAYLIST_VIEW;
329 	case 'q':
330 	case 'Q':
331 		return QUEUE_VIEW;
332 	default:
333 		return cur_view;
334 	}
335 }
336 
current_win(void)337 struct window *current_win(void)
338 {
339 	switch (cur_view) {
340 	case TREE_VIEW:
341 		return lib_cur_win;
342 	case SORTED_VIEW:
343 		return lib_editable.shared->win;
344 	case PLAYLIST_VIEW:
345 		return pl_cursor_win();
346 	case QUEUE_VIEW:
347 		return pq_editable.shared->win;
348 	case BROWSER_VIEW:
349 		return browser_win;
350 	case HELP_VIEW:
351 		return help_win;
352 	case FILTERS_VIEW:
353 	default:
354 		return filters_win;
355 	}
356 }
357 
cmd_add(char * arg)358 static void cmd_add(char *arg)
359 {
360 	int flag = parse_flags((const char **)&arg, "lpqQ");
361 
362 	if (flag == -1)
363 		return;
364 	if (arg == NULL) {
365 		error_msg("not enough arguments\n");
366 		return;
367 	}
368 	view_add(flag_to_view(flag), arg, flag == 'Q');
369 }
370 
cmd_clear(char * arg)371 static void cmd_clear(char *arg)
372 {
373 	int flag = parse_flags((const char **)&arg, "lpq");
374 
375 	if (flag == -1)
376 		return;
377 	if (arg) {
378 		error_msg("too many arguments\n");
379 		return;
380 	}
381 	view_clear(flag_to_view(flag));
382 }
383 
cmd_load(char * arg)384 static void cmd_load(char *arg)
385 {
386 	int flag = parse_flags((const char **)&arg, "lp");
387 
388 	if (flag == -1)
389 		return;
390 	if (arg == NULL) {
391 		error_msg("not enough arguments\n");
392 		return;
393 	}
394 	view_load(flag_to_view(flag), arg);
395 }
396 
cmd_save(char * arg)397 static void cmd_save(char *arg)
398 {
399 	int to_stdout = is_stdout_filename(arg);
400 	int flag = 0, f, extended = 0;
401 
402 	do {
403 		f = parse_one_flag((const char **)&arg, "eLlpq");
404 		if (f == 'e')
405 			extended = 1;
406 		else if (f)
407 			flag = f;
408 	} while (f > 0);
409 
410 	if (flag == -1)
411 		return;
412 	view_save(flag_to_view(flag), arg, to_stdout, flag == 'L', extended);
413 }
414 
cmd_set(char * arg)415 static void cmd_set(char *arg)
416 {
417 	char *value = NULL;
418 	int i;
419 
420 	for (i = 0; arg[i]; i++) {
421 		if (arg[i] == '=') {
422 			arg[i] = 0;
423 			value = &arg[i + 1];
424 			break;
425 		}
426 	}
427 	if (value) {
428 		option_set(arg, value);
429 		help_win->changed = 1;
430 		if (cur_view == TREE_VIEW) {
431 			lib_track_win->changed = 1;
432 			lib_tree_win->changed = 1;
433 		} else if (cur_view == PLAYLIST_VIEW) {
434 			pl_mark_for_redraw();
435 		} else {
436 			current_win()->changed = 1;
437 		}
438 		update_titleline();
439 		update_statusline();
440 	} else {
441 		struct cmus_opt *opt;
442 		char buf[OPTION_MAX_SIZE];
443 
444 		/* support "set <option>?" */
445 		i--;
446 		if (arg[i] == '?')
447 			arg[i] = 0;
448 
449 		opt = option_find(arg);
450 		if (opt) {
451 			opt->get(opt->data, buf, OPTION_MAX_SIZE);
452 			info_msg("setting: '%s=%s'", arg, buf);
453 		}
454 	}
455 }
456 
cmd_toggle(char * arg)457 static void cmd_toggle(char *arg)
458 {
459 	struct cmus_opt *opt = option_find(arg);
460 
461 	if (opt == NULL)
462 		return;
463 
464 	if (opt->toggle == NULL) {
465 		error_msg("%s is not toggle option", opt->name);
466 		return;
467 	}
468 	opt->toggle(opt->data);
469 	help_win->changed = 1;
470 	if (cur_view == TREE_VIEW) {
471 		lib_track_win->changed = 1;
472 		lib_tree_win->changed = 1;
473 	} else if (cur_view == PLAYLIST_VIEW) {
474 		pl_mark_for_redraw();
475 	} else {
476 		current_win()->changed = 1;
477 	}
478 	update_titleline();
479 	update_statusline();
480 }
481 
get_number(char * str,char ** end)482 static int get_number(char *str, char **end)
483 {
484 	int val = 0;
485 
486 	while (*str >= '0' && *str <= '9') {
487 		val *= 10;
488 		val += *str++ - '0';
489 	}
490 	*end = str;
491 	return val;
492 }
493 
cmd_seek(char * arg)494 static void cmd_seek(char *arg)
495 {
496 	int relative = 0;
497 	int seek = 0, sign = 1, count;
498 
499 	switch (*arg) {
500 	case '-':
501 		sign = -1;
502 		/* fallthrough */
503 	case '+':
504 		relative = 1;
505 		arg++;
506 		break;
507 	}
508 
509 	count = 0;
510 	goto inside;
511 
512 	do {
513 		int num;
514 		char *end;
515 
516 		if (*arg != ':')
517 			break;
518 		arg++;
519 inside:
520 		num = get_number(arg, &end);
521 		if (arg == end)
522 			break;
523 		arg = end;
524 		seek = seek * 60 + num;
525 	} while (++count < 3);
526 
527 	seek *= sign;
528 	if (!count)
529 		goto err;
530 
531 	if (count == 1) {
532 		switch (tolower((unsigned char)*arg)) {
533 		case 'h':
534 			seek *= 60;
535 			/* fallthrough */
536 		case 'm':
537 			seek *= 60;
538 			/* fallthrough */
539 		case 's':
540 			arg++;
541 			break;
542 		}
543 	}
544 
545 	if (!*arg) {
546 		player_seek(seek, relative, 0);
547 		return;
548 	}
549 err:
550 	error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
551 }
552 
cmd_factivate(char * arg)553 static void cmd_factivate(char *arg)
554 {
555 	filters_activate_names(arg);
556 }
557 
cmd_live_filter(char * arg)558 static void cmd_live_filter(char *arg)
559 {
560 	filters_set_live(arg);
561 }
562 
cmd_filter(char * arg)563 static void cmd_filter(char *arg)
564 {
565 	filters_set_anonymous(arg);
566 }
567 
cmd_fset(char * arg)568 static void cmd_fset(char *arg)
569 {
570 	filters_set_filter(arg);
571 }
572 
cmd_help(char * arg)573 static void cmd_help(char *arg)
574 {
575 	info_msg("To get started with cmus, read cmus-tutorial(7) and cmus(1) man pages");
576 }
577 
cmd_invert(char * arg)578 static void cmd_invert(char *arg)
579 {
580 	switch (cur_view) {
581 	case SORTED_VIEW:
582 		editable_invert_marks(&lib_editable);
583 		break;
584 	case PLAYLIST_VIEW:
585 		pl_invert_marks();
586 		break;
587 	case QUEUE_VIEW:
588 		editable_invert_marks(&pq_editable);
589 		break;
590 	default:
591 		info_msg(":invert only works in views 2-4");
592 	}
593 }
594 
cmd_mark(char * arg)595 static void cmd_mark(char *arg)
596 {
597 	switch (cur_view) {
598 	case SORTED_VIEW:
599 		editable_mark(&lib_editable, arg);
600 		break;
601 	case PLAYLIST_VIEW:
602 		pl_mark(arg);
603 		break;
604 	case QUEUE_VIEW:
605 		editable_mark(&pq_editable, arg);
606 		break;
607 	default:
608 		info_msg(":mark only works in views 2-4");
609 	}
610 }
611 
cmd_unmark(char * arg)612 static void cmd_unmark(char *arg)
613 {
614 	switch (cur_view) {
615 	case SORTED_VIEW:
616 		editable_unmark(&lib_editable);
617 		break;
618 	case PLAYLIST_VIEW:
619 		pl_unmark();
620 		break;
621 	case QUEUE_VIEW:
622 		editable_unmark(&pq_editable);
623 		break;
624 	default:
625 		info_msg(":unmark only works in views 2-4");
626 	}
627 }
628 
cmd_update_cache(char * arg)629 static void cmd_update_cache(char *arg)
630 {
631 	int flag = parse_flags((const char **)&arg, "f");
632 	cmus_update_cache(flag == 'f');
633 }
634 
cmd_cd(char * arg)635 static void cmd_cd(char *arg)
636 {
637 	if (arg) {
638 		char *dir, *absolute;
639 
640 		dir = expand_filename(arg);
641 		absolute = path_absolute(dir);
642 		if (chdir(dir) == -1) {
643 			error_msg("could not cd to '%s': %s", dir, strerror(errno));
644 		} else {
645 			browser_chdir(absolute);
646 		}
647 		free(absolute);
648 		free(dir);
649 	} else {
650 		if (chdir(home_dir) == -1) {
651 			error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
652 		} else {
653 			browser_chdir(home_dir);
654 		}
655 	}
656 }
657 
cmd_bind(char * arg)658 static void cmd_bind(char *arg)
659 {
660 	int flag = parse_flags((const char **)&arg, "f");
661 	char *key, *func;
662 
663 	if (flag == -1)
664 		return;
665 
666 	if (arg == NULL)
667 		goto err;
668 
669 	key = strchr(arg, ' ');
670 	if (key == NULL)
671 		goto err;
672 	*key++ = 0;
673 	while (*key == ' ')
674 		key++;
675 
676 	func = strchr(key, ' ');
677 	if (func == NULL)
678 		goto err;
679 	*func++ = 0;
680 	while (*func == ' ')
681 		func++;
682 	if (*func == 0)
683 		goto err;
684 
685 	key_bind(arg, key, func, flag == 'f');
686 	if (cur_view == HELP_VIEW)
687 		window_changed(help_win);
688 	return;
689 err:
690 	error_msg("expecting 3 arguments (context, key and function)\n");
691 }
692 
cmd_unbind(char * arg)693 static void cmd_unbind(char *arg)
694 {
695 	int flag = parse_flags((const char **)&arg, "f");
696 	char *key;
697 
698 	if (flag == -1)
699 		return;
700 
701 	if (arg == NULL)
702 		goto err;
703 
704 	key = strchr(arg, ' ');
705 	if (key == NULL)
706 		goto err;
707 	*key++ = 0;
708 	while (*key == ' ')
709 		key++;
710 	if (*key == 0)
711 		goto err;
712 
713 	strip_trailing_spaces(key);
714 
715 	key_unbind(arg, key, flag == 'f');
716 	return;
717 err:
718 	error_msg("expecting 2 arguments (context and key)\n");
719 }
720 
cmd_showbind(char * arg)721 static void cmd_showbind(char *arg)
722 {
723 	char *key;
724 
725 	key = strchr(arg, ' ');
726 	if (key == NULL)
727 		goto err;
728 	*key++ = 0;
729 	while (*key == ' ')
730 		key++;
731 	if (*key == 0)
732 		goto err;
733 
734 	strip_trailing_spaces(key);
735 
736 	show_binding(arg, key);
737 	return;
738 err:
739 	error_msg("expecting 2 arguments (context and key)\n");
740 }
741 
cmd_quit(char * arg)742 static void cmd_quit(char *arg)
743 {
744 	int flag = parse_flags((const char **)&arg, "i");
745 	enum ui_query_answer answer;
746 	if (!worker_has_job_by_type(JOB_TYPE_ANY)) {
747 		if (flag != 'i' || yes_no_query("Quit cmus? [y/N]") != UI_QUERY_ANSWER_NO)
748 			cmus_running = 0;
749 	} else {
750 		answer = yes_no_query("Tracks are being added. Quit and truncate playlist(s)? [y/N]");
751 		if (answer != UI_QUERY_ANSWER_NO)
752 			cmus_running = 0;
753 	}
754 }
755 
cmd_reshuffle(char * arg)756 static void cmd_reshuffle(char *arg)
757 {
758 	lib_reshuffle();
759 	pl_reshuffle();
760 }
761 
cmd_source(char * arg)762 static void cmd_source(char *arg)
763 {
764 	char *filename = expand_filename(arg);
765 
766 	if (source_file(filename) == -1)
767 		error_msg("sourcing %s: %s", filename, strerror(errno));
768 	free(filename);
769 }
770 
cmd_colorscheme(char * arg)771 static void cmd_colorscheme(char *arg)
772 {
773 	char filename[512];
774 
775 	snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
776 	if (source_file(filename) == -1) {
777 		snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_data_dir, arg);
778 		if (source_file(filename) == -1)
779 			error_msg("sourcing %s: %s", filename, strerror(errno));
780 	}
781 }
782 
783 /*
784  * \" inside double-quotes becomes "
785  * \\ inside double-quotes becomes \
786  */
parse_quoted(const char ** strp)787 static char *parse_quoted(const char **strp)
788 {
789 	const char *str = *strp;
790 	const char *start;
791 	char *ret, *dst;
792 
793 	str++;
794 	start = str;
795 	while (1) {
796 		int c = *str++;
797 
798 		if (c == 0)
799 			goto error;
800 		if (c == '"')
801 			break;
802 		if (c == '\\') {
803 			if (*str++ == 0)
804 				goto error;
805 		}
806 	}
807 	*strp = str;
808 	ret = xnew(char, str - start);
809 	str = start;
810 	dst = ret;
811 	while (1) {
812 		int c = *str++;
813 
814 		if (c == '"')
815 			break;
816 		if (c == '\\') {
817 			c = *str++;
818 			if (c != '"' && c != '\\')
819 				*dst++ = '\\';
820 		}
821 		*dst++ = c;
822 	}
823 	*dst = 0;
824 	return ret;
825 error:
826 	error_msg("`\"' expected");
827 	return NULL;
828 }
829 
parse_escaped(const char ** strp)830 static char *parse_escaped(const char **strp)
831 {
832 	const char *str = *strp;
833 	const char *start;
834 	char *ret, *dst;
835 
836 	start = str;
837 	while (1) {
838 		int c = *str;
839 
840 		if (c == 0 || c == ' ' || c == '\'' || c == '"')
841 			break;
842 
843 		str++;
844 		if (c == '\\') {
845 			c = *str;
846 			if (c == 0)
847 				break;
848 			str++;
849 		}
850 	}
851 	*strp = str;
852 	ret = xnew(char, str - start + 1);
853 	str = start;
854 	dst = ret;
855 	while (1) {
856 		int c = *str;
857 
858 		if (c == 0 || c == ' ' || c == '\'' || c == '"')
859 			break;
860 
861 		str++;
862 		if (c == '\\') {
863 			c = *str;
864 			if (c == 0) {
865 				*dst++ = '\\';
866 				break;
867 			}
868 			str++;
869 		}
870 		*dst++ = c;
871 	}
872 	*dst = 0;
873 	return ret;
874 }
875 
parse_one(const char ** strp)876 static char *parse_one(const char **strp)
877 {
878 	const char *str = *strp;
879 	char *ret = NULL;
880 
881 	while (1) {
882 		char *part = NULL;
883 		int c = *str;
884 
885 		if (!c || c == ' ')
886 			break;
887 		if (c == '"') {
888 			part = parse_quoted(&str);
889 			if (part == NULL)
890 				goto error;
891 		} else if (c == '\'') {
892 			/* backslashes are normal chars inside single-quotes */
893 			const char *end;
894 
895 			str++;
896 			end = strchr(str, '\'');
897 			if (end == NULL)
898 				goto sq_missing;
899 			part = xstrndup(str, end - str);
900 			str = end + 1;
901 		} else {
902 			part = parse_escaped(&str);
903 		}
904 
905 		if (ret == NULL) {
906 			ret = xstrdup(part);
907 		} else {
908 			char *tmp = xstrjoin(ret, part);
909 			free(ret);
910 			ret = tmp;
911 		}
912 		free(part);
913 	}
914 	*strp = str;
915 	return ret;
916 sq_missing:
917 	error_msg("`'' expected");
918 error:
919 	free(ret);
920 	return NULL;
921 }
922 
parse_cmd(const char * cmd,int * args_idx,int * ac)923 char **parse_cmd(const char *cmd, int *args_idx, int *ac)
924 {
925 	char **av = NULL;
926 	int nr = 0;
927 	int alloc = 0;
928 
929 	while (*cmd) {
930 		char *arg;
931 
932 		/* there can't be spaces at start of command
933 		 * and there is at least one argument */
934 		if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
935 			/* {} is replaced with file arguments */
936 			if (*args_idx != -1)
937 				goto only_once_please;
938 			*args_idx = nr;
939 			cmd += 2;
940 			goto skip_spaces;
941 		} else {
942 			arg = parse_one(&cmd);
943 			if (arg == NULL)
944 				goto error;
945 		}
946 
947 		if (nr == alloc) {
948 			alloc = alloc ? alloc * 2 : 4;
949 			av = xrenew(char *, av, alloc + 1);
950 		}
951 		av[nr++] = arg;
952 skip_spaces:
953 		while (*cmd == ' ')
954 			cmd++;
955 	}
956 	av[nr] = NULL;
957 	*ac = nr;
958 	return av;
959 only_once_please:
960 	error_msg("{} can be used only once");
961 error:
962 	while (nr > 0)
963 		free(av[--nr]);
964 	free(av);
965 	return NULL;
966 }
967 
968 struct track_info_selection {
969 	struct track_info **tis;
970 	int tis_alloc;
971 	int tis_nr;
972 };
973 
add_ti(void * data,struct track_info * ti)974 static int add_ti(void *data, struct track_info *ti)
975 {
976 	struct track_info_selection *sel = data;
977 	if (sel->tis_nr == sel->tis_alloc) {
978 		sel->tis_alloc = sel->tis_alloc ? sel->tis_alloc * 2 : 8;
979 		sel->tis = xrenew(struct track_info *, sel->tis, sel->tis_alloc);
980 	}
981 	track_info_ref(ti);
982 	sel->tis[sel->tis_nr++] = ti;
983 	return 0;
984 }
985 
cmd_run(char * arg)986 static void cmd_run(char *arg)
987 {
988 	char **av, **argv;
989 	int ac, argc, i, run, files_idx = -1;
990 	struct track_info_selection sel = { .tis = NULL };
991 
992 	if (cur_view > QUEUE_VIEW) {
993 		info_msg("Command execution is supported only in views 1-4");
994 		return;
995 	}
996 
997 	av = parse_cmd(arg, &files_idx, &ac);
998 	if (av == NULL) {
999 		return;
1000 	}
1001 
1002 	/* collect selected files (struct track_info) */
1003 	switch (cur_view) {
1004 	case TREE_VIEW:
1005 		_tree_for_each_sel(add_ti, &sel, 0);
1006 		break;
1007 	case SORTED_VIEW:
1008 		_editable_for_each_sel(&lib_editable, add_ti, &sel, 0);
1009 		break;
1010 	case PLAYLIST_VIEW:
1011 		_pl_for_each_sel(add_ti, &sel, 0);
1012 		break;
1013 	case QUEUE_VIEW:
1014 		_editable_for_each_sel(&pq_editable, add_ti, &sel, 0);
1015 		break;
1016 	}
1017 
1018 	if (sel.tis_nr == 0) {
1019 		/* no files selected, do nothing */
1020 		free_str_array(av);
1021 		return;
1022 	}
1023 	sel.tis[sel.tis_nr] = NULL;
1024 
1025 	/* build argv */
1026 	argv = xnew(char *, ac + sel.tis_nr + 1);
1027 	argc = 0;
1028 	if (files_idx == -1) {
1029 		/* add selected files after rest of the args */
1030 		for (i = 0; i < ac; i++)
1031 			argv[argc++] = av[i];
1032 		for (i = 0; i < sel.tis_nr; i++)
1033 			argv[argc++] = sel.tis[i]->filename;
1034 	} else {
1035 		for (i = 0; i < files_idx; i++)
1036 			argv[argc++] = av[i];
1037 		for (i = 0; i < sel.tis_nr; i++)
1038 			argv[argc++] = sel.tis[i]->filename;
1039 		for (i = files_idx; i < ac; i++)
1040 			argv[argc++] = av[i];
1041 	}
1042 	argv[argc] = NULL;
1043 
1044 	for (i = 0; argv[i]; i++)
1045 		d_print("ARG: '%s'\n", argv[i]);
1046 
1047 	run = 1;
1048 	if (confirm_run && (sel.tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
1049 		if (yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel.tis_nr) != UI_QUERY_ANSWER_YES) {
1050 			info_msg("Aborted");
1051 			run = 0;
1052 		}
1053 	}
1054 	if (run) {
1055 		int status;
1056 
1057 		if (spawn(argv, &status, 1)) {
1058 			error_msg("executing %s: %s", argv[0], strerror(errno));
1059 		} else {
1060 			if (WIFEXITED(status)) {
1061 				int rc = WEXITSTATUS(status);
1062 
1063 				if (rc)
1064 					error_msg("%s returned %d", argv[0], rc);
1065 			}
1066 			if (WIFSIGNALED(status))
1067 				error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1068 
1069 			switch (cur_view) {
1070 			case TREE_VIEW:
1071 			case SORTED_VIEW:
1072 				/* this must be done before sel.tis are unreffed */
1073 				free_str_array(av);
1074 				free(argv);
1075 
1076 				/* remove non-existed files, update tags for changed files */
1077 				cmus_update_tis(sel.tis, sel.tis_nr, 0);
1078 
1079 				/* we don't own sel.tis anymore! */
1080 				return;
1081 			}
1082 		}
1083 	}
1084 	free_str_array(av);
1085 	free(argv);
1086 	for (i = 0; sel.tis[i]; i++)
1087 		track_info_unref(sel.tis[i]);
1088 	free(sel.tis);
1089 }
1090 
cmd_shell(char * arg)1091 static void cmd_shell(char *arg)
1092 {
1093 	const char * const argv[] = { "sh", "-c", arg, NULL };
1094 
1095 	if (spawn((char **) argv, NULL, 0))
1096 		error_msg("executing '%s': %s", arg, strerror(errno));
1097 }
1098 
get_one_ti(void * data,struct track_info * ti)1099 static int get_one_ti(void *data, struct track_info *ti)
1100 {
1101 	struct track_info **sel_ti = data;
1102 
1103 	track_info_ref(ti);
1104 	*sel_ti = ti;
1105 	/* stop the for each loop, we need only the first selected track */
1106 	return 1;
1107 }
1108 
cmd_echo(char * arg)1109 static void cmd_echo(char *arg)
1110 {
1111 	struct track_info *sel_ti;
1112 	char *ptr = arg;
1113 
1114 	while (1) {
1115 		ptr = strchr(ptr, '{');
1116 		if (ptr == NULL)
1117 			break;
1118 		if (ptr[1] == '}')
1119 			break;
1120 		ptr++;
1121 	}
1122 
1123 	if (ptr == NULL) {
1124 		info_msg("%s", arg);
1125 		return;
1126 	}
1127 
1128 	if (cur_view > QUEUE_VIEW) {
1129 		info_msg("echo with {} in its arguments is supported only in views 1-4");
1130 		return;
1131 	}
1132 
1133 	*ptr = 0;
1134 	ptr += 2;
1135 
1136 	/* get only the first selected track */
1137 	sel_ti = NULL;
1138 
1139 	switch (cur_view) {
1140 	case TREE_VIEW:
1141 		_tree_for_each_sel(get_one_ti, &sel_ti, 0);
1142 		break;
1143 	case SORTED_VIEW:
1144 		_editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1145 		break;
1146 	case PLAYLIST_VIEW:
1147 		_pl_for_each_sel(get_one_ti, &sel_ti, 0);
1148 		break;
1149 	case QUEUE_VIEW:
1150 		_editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1151 		break;
1152 	}
1153 
1154 	if (sel_ti == NULL)
1155 		return;
1156 
1157 	info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1158 	track_info_unref(sel_ti);
1159 }
1160 
parse_vol_arg(const char * arg,int * value,unsigned int * flags)1161 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1162 {
1163 	unsigned int f = 0;
1164 	int ch, val = 0, digits = 0, sign = 1;
1165 
1166 	if (*arg == '-') {
1167 		arg++;
1168 		f |= VF_RELATIVE;
1169 		sign = -1;
1170 	} else if (*arg == '+') {
1171 		arg++;
1172 		f |= VF_RELATIVE;
1173 	}
1174 
1175 	while (1) {
1176 		ch = *arg++;
1177 		if (ch < '0' || ch > '9')
1178 			break;
1179 		val *= 10;
1180 		val += ch - '0';
1181 		digits++;
1182 	}
1183 	if (digits == 0)
1184 		goto err;
1185 
1186 	if (ch == '%') {
1187 		f |= VF_PERCENTAGE;
1188 		ch = *arg;
1189 	}
1190 	if (ch)
1191 		goto err;
1192 
1193 	*value = sign * val;
1194 	*flags = f;
1195 	return 0;
1196 err:
1197 	return -1;
1198 }
1199 
cmd_mute(char * arg)1200 static void cmd_mute(char *arg)
1201 {
1202 	int l = 0, r = 0;
1203 
1204 	if (volume_l == 0 && volume_r == 0) {
1205 		// unmute
1206 		l = mute_vol_l;
1207 		r = mute_vol_r;
1208 	} else {
1209 		mute_vol_l = volume_l;
1210 		mute_vol_r = volume_r;
1211 	}
1212 
1213 	int rc = player_set_vol(l, 0, r, 0);
1214 	if (rc != OP_ERROR_SUCCESS) {
1215 		char *msg = op_get_error_msg(rc, "can't change volume");
1216 		error_msg("%s", msg);
1217 		free(msg);
1218 	} else {
1219 		mpris_volume_changed();
1220 	}
1221 	update_statusline();
1222 }
1223 
1224 
1225 /*
1226  * :vol value [value]
1227  *
1228  * where value is [-+]?[0-9]+%?
1229  */
cmd_vol(char * arg)1230 static void cmd_vol(char *arg)
1231 {
1232 	char **values = get_words(arg);
1233 	unsigned int lf, rf;
1234 	int l, r;
1235 
1236 	if (values[1] && values[2])
1237 		goto err;
1238 
1239 	if (parse_vol_arg(values[0], &l, &lf))
1240 		goto err;
1241 
1242 	r = l;
1243 	rf = lf;
1244 	if (values[1] && parse_vol_arg(values[1], &r, &rf))
1245 		goto err;
1246 
1247 	free_str_array(values);
1248 
1249 	int rc = player_set_vol(l, lf, r, rf);
1250 	if (rc != OP_ERROR_SUCCESS) {
1251 		char *msg = op_get_error_msg(rc, "can't change volume");
1252 		error_msg("%s", msg);
1253 		free(msg);
1254 	} else {
1255 		mpris_volume_changed();
1256 	}
1257 	update_statusline();
1258 	return;
1259 err:
1260 	free_str_array(values);
1261 	error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1262 }
1263 
cmd_prev_view(char * arg)1264 static void cmd_prev_view(char *arg)
1265 {
1266 	if (prev_view >= 0) {
1267 		set_view(prev_view);
1268 	}
1269 }
1270 
cmd_left_view(char * arg)1271 static void cmd_left_view(char *arg)
1272 {
1273 	if (cur_view == TREE_VIEW) {
1274 		set_view(HELP_VIEW);
1275 	} else {
1276 		set_view(cur_view - 1);
1277 	}
1278 }
1279 
cmd_right_view(char * arg)1280 static void cmd_right_view(char *arg)
1281 {
1282 	if (cur_view == HELP_VIEW) {
1283 		set_view(TREE_VIEW);
1284 	} else {
1285 		set_view(cur_view + 1);
1286 	}
1287 }
1288 
cmd_pl_create(char * arg)1289 static void cmd_pl_create(char *arg)
1290 {
1291 	pl_create(arg);
1292 }
1293 
cmd_pl_export(char * arg)1294 static void cmd_pl_export(char *arg)
1295 {
1296 	if (cur_view == PLAYLIST_VIEW)
1297 		pl_export_selected_pl(arg);
1298 	else
1299 		info_msg(":pl-export only works in view 3");
1300 }
1301 
get_browser_add_file(void)1302 static char *get_browser_add_file(void)
1303 {
1304 	char *sel = browser_get_sel();
1305 
1306 	if (sel && (ends_with(sel, "/../") || ends_with(sel, "/.."))) {
1307 		info_msg("For convenience, you can not add \"..\" directory from the browser view");
1308 		free(sel);
1309 		sel = NULL;
1310 	}
1311 
1312 	return sel;
1313 }
1314 
cmd_pl_import(char * arg)1315 static void cmd_pl_import(char *arg)
1316 {
1317 	char *name = NULL;
1318 
1319 	if (arg)
1320 		name = view_load_prepare(arg);
1321 	else if (cur_view == BROWSER_VIEW)
1322 		name = get_browser_add_file();
1323 	else
1324 		error_msg("not enough arguments");
1325 
1326 	if (name) {
1327 		pl_import(name);
1328 		free(name);
1329 	}
1330 }
1331 
cmd_pl_rename(char * arg)1332 static void cmd_pl_rename(char *arg)
1333 {
1334 	if (cur_view == PLAYLIST_VIEW)
1335 		pl_rename_selected_pl(arg);
1336 	else
1337 		info_msg(":pl-rename only works in view 3");
1338 }
1339 
cmd_version(char * arg)1340 static void cmd_version(char *arg)
1341 {
1342 	info_msg(VERSION);
1343 }
1344 
cmd_view(char * arg)1345 static void cmd_view(char *arg)
1346 {
1347 	int view;
1348 
1349 	if (parse_enum(arg, 1, NR_VIEWS, view_names, &view) && (view - 1) != cur_view) {
1350 		set_view(view - 1);
1351 	}
1352 }
1353 
cmd_push(char * arg)1354 static void cmd_push(char *arg)
1355 {
1356 	if (arg)
1357 		cmdline_set_text(arg);
1358 	enter_command_mode();
1359 }
1360 
cmd_p_next(char * arg)1361 static void cmd_p_next(char *arg)
1362 {
1363 	cmus_next();
1364 }
1365 
cmd_p_pause(char * arg)1366 static void cmd_p_pause(char *arg)
1367 {
1368 	player_pause();
1369 }
1370 
cmd_p_pause_playback(char * arg)1371 static void cmd_p_pause_playback(char *arg)
1372 {
1373 	player_pause_playback();
1374 }
1375 
cmd_p_play(char * arg)1376 static void cmd_p_play(char *arg)
1377 {
1378 	if (arg) {
1379 		char *tmp = expand_filename(arg);
1380 		cmus_play_file(tmp);
1381 		free(tmp);
1382 	} else {
1383 		player_play();
1384 	}
1385 }
1386 
cmd_p_prev(char * arg)1387 static void cmd_p_prev(char *arg)
1388 {
1389 	if (rewind_offset < 0 || player_info.pos < rewind_offset) {
1390 		cmus_prev();
1391 	} else {
1392 		player_play();
1393 	}
1394 }
1395 
cmd_p_stop(char * arg)1396 static void cmd_p_stop(char *arg)
1397 {
1398 	player_stop();
1399 }
1400 
cmd_pwd(char * arg)1401 static void cmd_pwd(char *arg)
1402 {
1403 	char buf[4096];
1404 	if (getcwd(buf, sizeof buf)) {
1405 		info_msg("%s", buf);
1406 	}
1407 }
1408 
cmd_raise_vte(char * arg)1409 static void cmd_raise_vte(char *arg)
1410 {
1411 	cmus_raise_vte();
1412 }
1413 
cmd_rand(char * arg)1414 static void cmd_rand(char *arg)
1415 {
1416 	switch (cur_view) {
1417 	case TREE_VIEW:
1418 		break;
1419 	case SORTED_VIEW:
1420 		editable_rand(&lib_editable);
1421 		break;
1422 	case PLAYLIST_VIEW:
1423 		pl_rand();
1424 		break;
1425 	case QUEUE_VIEW:
1426 		editable_rand(&pq_editable);
1427 		break;
1428 	}
1429 }
1430 
cmd_search_next(char * arg)1431 static void cmd_search_next(char *arg)
1432 {
1433 	if (search_str) {
1434 		if (!search_next(searchable, search_str, search_direction))
1435 			search_not_found();
1436 	}
1437 }
1438 
cmd_search_prev(char * arg)1439 static void cmd_search_prev(char *arg)
1440 {
1441 	if (search_str) {
1442 		if (!search_next(searchable, search_str, !search_direction))
1443 			search_not_found();
1444 	}
1445 }
1446 
cmd_search_start(char * arg)1447 static void cmd_search_start(char *arg)
1448 {
1449 	enter_search_mode();
1450 }
1451 
cmd_search_b_start(char * arg)1452 static void cmd_search_b_start(char *arg)
1453 {
1454 	enter_search_backward_mode();
1455 }
1456 
sorted_for_each_sel(track_info_cb cb,void * data,int reverse,int advance)1457 static int sorted_for_each_sel(track_info_cb cb, void *data, int reverse, int advance)
1458 {
1459 	return editable_for_each_sel(&lib_editable, cb, data, reverse, advance);
1460 }
1461 
pq_for_each_sel(track_info_cb cb,void * data,int reverse,int advance)1462 static int pq_for_each_sel(track_info_cb cb, void *data, int reverse, int advance)
1463 {
1464 	return editable_for_each_sel(&pq_editable, cb, data, reverse, advance);
1465 }
1466 
1467 static for_each_sel_ti_cb view_for_each_sel[4] = {
1468 	tree_for_each_sel,
1469 	sorted_for_each_sel,
1470 	pl_for_each_sel,
1471 	pq_for_each_sel
1472 };
1473 
1474 /* wrapper for add_ti_cb, (void *) can't store function pointers */
1475 struct wrapper_cb_data {
1476 	add_ti_cb cb;
1477 };
1478 
1479 /* wrapper for void lib_add_track(struct track_info *) etc. */
wrapper_cb(void * data,struct track_info * ti)1480 static int wrapper_cb(void *data, struct track_info *ti)
1481 {
1482 	struct wrapper_cb_data *add = data;
1483 
1484 	add->cb(ti, NULL);
1485 	return 0;
1486 }
1487 
add_from_browser(add_ti_cb add,int job_type,int advance)1488 static void add_from_browser(add_ti_cb add, int job_type, int advance)
1489 {
1490 	char *sel = get_browser_add_file();
1491 
1492 	if (sel) {
1493 		enum file_type ft;
1494 		char *ret;
1495 
1496 		ft = cmus_detect_ft(sel, &ret);
1497 		if (ft != FILE_TYPE_INVALID) {
1498 			cmus_add(add, ret, ft, job_type, 0, NULL);
1499 			if (advance)
1500 				window_down(browser_win, 1);
1501 		}
1502 		free(ret);
1503 		free(sel);
1504 	}
1505 }
1506 
cmd_win_add_l(char * arg)1507 static void cmd_win_add_l(char *arg)
1508 {
1509 	int flag = parse_flags((const char **)&arg, "n");
1510 	if (flag == -1)
1511 		return;
1512 
1513 	if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1514 		return;
1515 
1516 	if (cur_view <= QUEUE_VIEW) {
1517 		struct wrapper_cb_data add = { lib_add_track };
1518 		view_for_each_sel[cur_view](wrapper_cb, &add, 0, flag != 'n');
1519 	} else if (cur_view == BROWSER_VIEW) {
1520 		add_from_browser(lib_add_track, JOB_TYPE_LIB, flag != 'n');
1521 	}
1522 }
1523 
cmd_win_add_p(char * arg)1524 static void cmd_win_add_p(char *arg)
1525 {
1526 	int flag = parse_flags((const char **)&arg, "n");
1527 	if (flag == -1)
1528 		return;
1529 
1530 	if (cur_view == PLAYLIST_VIEW && pl_visible_is_marked())
1531 		return;
1532 
1533 	if (cur_view <= QUEUE_VIEW) {
1534 		struct wrapper_cb_data add = { pl_add_track_to_marked_pl2 };
1535 		view_for_each_sel[cur_view](wrapper_cb, &add, 0, flag != 'n');
1536 	} else if (cur_view == BROWSER_VIEW) {
1537 		char *sel = get_browser_add_file();
1538 		if (sel) {
1539 			if (pl_add_file_to_marked_pl(sel) && flag != 'n')
1540 				window_down(browser_win, 1);
1541 			free(sel);
1542 		}
1543 	}
1544 }
1545 
cmd_win_add_Q(char * arg)1546 static void cmd_win_add_Q(char *arg)
1547 {
1548 	int flag = parse_flags((const char **)&arg, "n");
1549 	if (flag == -1)
1550 		return;
1551 
1552 	if (cur_view == QUEUE_VIEW)
1553 		return;
1554 
1555 	if (cur_view <= QUEUE_VIEW) {
1556 		struct wrapper_cb_data add = { play_queue_prepend };
1557 		view_for_each_sel[cur_view](wrapper_cb, &add, 1, flag != 'n');
1558 	} else if (cur_view == BROWSER_VIEW) {
1559 		add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE, flag != 'n');
1560 	}
1561 }
1562 
cmd_win_add_q(char * arg)1563 static void cmd_win_add_q(char *arg)
1564 {
1565 	int flag = parse_flags((const char **)&arg, "n");
1566 	if (flag == -1)
1567 		return;
1568 
1569 	if (cur_view == QUEUE_VIEW)
1570 		return;
1571 
1572 	if (cur_view <= QUEUE_VIEW) {
1573 		struct wrapper_cb_data add = { play_queue_append };
1574 		view_for_each_sel[cur_view](wrapper_cb, &add, 0, flag != 'n');
1575 	} else if (cur_view == BROWSER_VIEW) {
1576 		add_from_browser(play_queue_append, JOB_TYPE_QUEUE, flag != 'n');
1577 	}
1578 }
1579 
cmd_win_activate(char * arg)1580 static void cmd_win_activate(char *arg)
1581 {
1582 	struct track_info *info = NULL;
1583 	struct shuffle_track *previous = NULL, *next = NULL;
1584 	struct rb_root *shuffle_root = NULL;
1585 
1586 	if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW) {
1587 		if (lib_cur_track)
1588 			previous = &lib_cur_track->shuffle_track;
1589 		shuffle_root = &lib_shuffle_root;
1590 	}
1591 
1592 	switch (cur_view) {
1593 	case TREE_VIEW:
1594 		info = tree_activate_selected();
1595 		next = &lib_cur_track->shuffle_track;
1596 		break;
1597 	case SORTED_VIEW:
1598 		info = sorted_activate_selected();
1599 		next = &lib_cur_track->shuffle_track;
1600 		break;
1601 	case PLAYLIST_VIEW:
1602 		info = pl_play_selected_row();
1603 		break;
1604 	case QUEUE_VIEW:
1605 		break;
1606 	case BROWSER_VIEW:
1607 		browser_enter();
1608 		break;
1609 	case FILTERS_VIEW:
1610 		filters_activate(1);
1611 		break;
1612 	case HELP_VIEW:
1613 		help_select();
1614 		break;
1615 	}
1616 
1617 	if (info) {
1618 		if (shuffle && next)
1619 			shuffle_insert(shuffle_root, previous, next);
1620 		/* update lib/pl mode */
1621 		if (cur_view < 2)
1622 			play_library = 1;
1623 		if (cur_view == 2)
1624 			play_library = 0;
1625 
1626 		player_play_file(info);
1627 	}
1628 }
1629 
cmd_win_mv_after(char * arg)1630 static void cmd_win_mv_after(char *arg)
1631 {
1632 	switch (cur_view) {
1633 	case SORTED_VIEW:
1634 		editable_move_after(&lib_editable);
1635 		break;
1636 	case PLAYLIST_VIEW:
1637 		pl_win_mv_after();
1638 		break;
1639 	case QUEUE_VIEW:
1640 		editable_move_after(&pq_editable);
1641 		break;
1642 	}
1643 }
1644 
cmd_win_mv_before(char * arg)1645 static void cmd_win_mv_before(char *arg)
1646 {
1647 	switch (cur_view) {
1648 	case SORTED_VIEW:
1649 		editable_move_before(&lib_editable);
1650 		break;
1651 	case PLAYLIST_VIEW:
1652 		pl_win_mv_before();
1653 		break;
1654 	case QUEUE_VIEW:
1655 		editable_move_before(&pq_editable);
1656 		break;
1657 	}
1658 }
1659 
cmd_win_remove(char * arg)1660 static void cmd_win_remove(char *arg)
1661 {
1662 	switch (cur_view) {
1663 	case TREE_VIEW:
1664 		tree_remove_sel();
1665 		break;
1666 	case SORTED_VIEW:
1667 		editable_remove_sel(&lib_editable);
1668 		break;
1669 	case PLAYLIST_VIEW:
1670 		pl_win_remove();
1671 		break;
1672 	case QUEUE_VIEW:
1673 		editable_remove_sel(&pq_editable);
1674 		break;
1675 	case BROWSER_VIEW:
1676 		browser_delete();
1677 		break;
1678 	case FILTERS_VIEW:
1679 		filters_delete_filter();
1680 		break;
1681 	case HELP_VIEW:
1682 		help_remove();
1683 		break;
1684 	}
1685 }
1686 
cmd_win_sel_cur(char * arg)1687 static void cmd_win_sel_cur(char *arg)
1688 {
1689 	switch (cur_view) {
1690 	case TREE_VIEW:
1691 		tree_sel_current(auto_expand_albums_selcur);
1692 		break;
1693 	case SORTED_VIEW:
1694 		sorted_sel_current();
1695 		break;
1696 	case PLAYLIST_VIEW:
1697 		pl_select_playing_track();
1698 		break;
1699 	}
1700 }
1701 
cmd_win_toggle(char * arg)1702 static void cmd_win_toggle(char *arg)
1703 {
1704 	switch (cur_view) {
1705 	case TREE_VIEW:
1706 		tree_toggle_expand_artist();
1707 		break;
1708 	case SORTED_VIEW:
1709 		editable_toggle_mark(&lib_editable);
1710 		break;
1711 	case PLAYLIST_VIEW:
1712 		pl_win_toggle();
1713 		break;
1714 	case QUEUE_VIEW:
1715 		editable_toggle_mark(&pq_editable);
1716 		break;
1717 	case FILTERS_VIEW:
1718 		filters_toggle_filter();
1719 		break;
1720 	case HELP_VIEW:
1721 		help_toggle();
1722 		break;
1723 	}
1724 }
1725 
cmd_win_scroll_down(char * arg)1726 static void cmd_win_scroll_down(char *arg)
1727 {
1728 	window_scroll_down(current_win());
1729 }
1730 
cmd_win_scroll_up(char * arg)1731 static void cmd_win_scroll_up(char *arg)
1732 {
1733 	window_scroll_up(current_win());
1734 }
1735 
cmd_win_bottom(char * arg)1736 static void cmd_win_bottom(char *arg)
1737 {
1738 	window_goto_bottom(current_win());
1739 }
1740 
cmd_win_down(char * arg)1741 static void cmd_win_down(char *arg)
1742 {
1743 	unsigned num_rows = 1;
1744 	char *end;
1745 
1746 	if (arg) {
1747 		if ((num_rows = get_number(arg, &end)) == 0 || *end) {
1748 			error_msg("invalid argument\n");
1749 			return;
1750 		}
1751 	}
1752 
1753 	window_down(current_win(), num_rows);
1754 }
1755 
cmd_win_next(char * arg)1756 static void cmd_win_next(char *arg)
1757 {
1758 	if (cur_view == TREE_VIEW)
1759 		tree_toggle_active_window();
1760 	else if (cur_view == PLAYLIST_VIEW)
1761 		pl_win_next();
1762 }
1763 
cmd_win_pg_down(char * arg)1764 static void cmd_win_pg_down(char *arg)
1765 {
1766 	window_page_down(current_win());
1767 }
1768 
cmd_win_pg_up(char * arg)1769 static void cmd_win_pg_up(char *arg)
1770 {
1771 	window_page_up(current_win());
1772 }
1773 
cmd_win_hf_pg_down(char * arg)1774 static void cmd_win_hf_pg_down(char *arg)
1775 {
1776 	window_half_page_down(current_win());
1777 }
1778 
cmd_win_hf_pg_up(char * arg)1779 static void cmd_win_hf_pg_up(char *arg)
1780 {
1781 	window_half_page_up(current_win());
1782 }
1783 
cmd_win_pg_top(char * arg)1784 static void cmd_win_pg_top(char *arg)
1785 {
1786 	window_page_top(current_win());
1787 }
1788 
cmd_win_pg_bottom(char * arg)1789 static void cmd_win_pg_bottom(char *arg)
1790 {
1791 	window_page_bottom(current_win());
1792 }
1793 
cmd_win_pg_middle(char * arg)1794 static void cmd_win_pg_middle(char *arg)
1795 {
1796 	window_page_middle(current_win());
1797 }
1798 
cmd_win_update_cache(char * arg)1799 static void cmd_win_update_cache(char *arg)
1800 {
1801 	struct track_info_selection sel = { .tis = NULL };
1802 	int flag = parse_flags((const char **)&arg, "f");
1803 
1804 	if (cur_view != TREE_VIEW && cur_view != SORTED_VIEW)
1805 		return;
1806 
1807 	view_for_each_sel[cur_view](add_ti, &sel, 0, 1);
1808 	if (sel.tis_nr == 0)
1809 		return;
1810 	sel.tis[sel.tis_nr] = NULL;
1811 	cmus_update_tis(sel.tis, sel.tis_nr, flag == 'f');
1812 }
1813 
cmd_win_top(char * arg)1814 static void cmd_win_top(char *arg)
1815 {
1816 	window_goto_top(current_win());
1817 }
1818 
cmd_win_up(char * arg)1819 static void cmd_win_up(char *arg)
1820 {
1821 	unsigned num_rows = 1;
1822 	char *end;
1823 
1824 	if (arg) {
1825 		if ((num_rows = get_number(arg, &end)) == 0 || *end) {
1826 			error_msg("invalid argument\n");
1827 			return;
1828 		}
1829 	}
1830 
1831 	window_up(current_win(), num_rows);
1832 }
1833 
cmd_win_update(char * arg)1834 static void cmd_win_update(char *arg)
1835 {
1836 	switch (cur_view) {
1837 	case TREE_VIEW:
1838 	case SORTED_VIEW:
1839 		cmus_update_lib();
1840 		break;
1841 	case PLAYLIST_VIEW:
1842 		pl_win_update();
1843 		break;
1844 	case BROWSER_VIEW:
1845 		browser_reload();
1846 		break;
1847 	}
1848 }
1849 
cmd_browser_up(char * arg)1850 static void cmd_browser_up(char *arg)
1851 {
1852 	browser_up();
1853 }
1854 
cmd_refresh(char * arg)1855 static void cmd_refresh(char *arg)
1856 {
1857 	clearok(curscr, TRUE);
1858 	refresh();
1859 }
1860 
cmp_intp(const void * ap,const void * bp)1861 static int cmp_intp(const void *ap, const void *bp)
1862 {
1863 	int a = *(int *)ap;
1864 	int b = *(int *)bp;
1865 	return a - b;
1866 }
1867 
rand_array(int size,int nmax)1868 static int *rand_array(int size, int nmax)
1869 {
1870 	int *r = xnew(int, size + 1);
1871 	int i, offset = 0;
1872 	int count = size;
1873 
1874 	if (count > nmax / 2) {
1875 		/*
1876 		 * Imagine that there are 1000 tracks in library and we want to
1877 		 * add 998 random tracks to queue.  After we have added 997
1878 		 * random numbers to the array it would be quite hard to find a
1879 		 * random number that isn't already in the array (3/1000
1880 		 * probability).
1881 		 *
1882 		 * So we invert the logic:
1883 		 *
1884 		 * Find two (1000 - 998) random numbers in 0..999 range and put
1885 		 * them at end of the array.  Sort the numbers and then fill
1886 		 * the array starting at index 0 with incrementing values that
1887 		 * are not in the set of random numbers.
1888 		 */
1889 		count = nmax - count;
1890 		offset = size - count;
1891 	}
1892 
1893 	for (i = 0; i < count; ) {
1894 		int v, j;
1895 found:
1896 		v = rand() % nmax;
1897 		for (j = 0; j < i; j++) {
1898 			if (r[offset + j] == v)
1899 				goto found;
1900 		}
1901 		r[offset + i++] = v;
1902 	}
1903 	qsort(r + offset, count, sizeof(*r), cmp_intp);
1904 
1905 	if (offset) {
1906 		int j, n;
1907 
1908 		/* simplifies next loop */
1909 		r[size] = nmax;
1910 
1911 		/* convert the indexes we don't want to those we want */
1912 		i = 0;
1913 		j = offset;
1914 		n = 0;
1915 		do {
1916 			while (n < r[j])
1917 				r[i++] = n++;
1918 			j++;
1919 			n++;
1920 		} while (i < size);
1921 	}
1922 	return r;
1923 }
1924 
count_albums(void)1925 static int count_albums(void)
1926 {
1927 	struct artist *artist;
1928 	struct rb_node *tmp1, *tmp2;
1929 	int count = 0;
1930 
1931 	rb_for_each_entry(artist, tmp1, &lib_artist_root, tree_node) {
1932 		rb_for_each(tmp2, &artist->album_root)
1933 			count++;
1934 	}
1935 	return count;
1936 }
1937 
1938 struct album_list {
1939 	struct list_head node;
1940 	const struct album *album;
1941 };
1942 
cmd_lqueue(char * arg)1943 static void cmd_lqueue(char *arg)
1944 {
1945 	LIST_HEAD(head);
1946 	const struct list_head *item;
1947 	const struct album *album;
1948 	int count = 1, nmax, i, pos;
1949 	int *r;
1950 
1951 	if (arg) {
1952 		long int val;
1953 
1954 		if (str_to_int(arg, &val) || val <= 0) {
1955 			error_msg("argument must be positive integer");
1956 			return;
1957 		}
1958 		count = val;
1959 	}
1960 	nmax = count_albums();
1961 	if (count > nmax)
1962 		count = nmax;
1963 	if (!count)
1964 		return;
1965 
1966 	r = rand_array(count, nmax);
1967 	album = to_album(rb_first(&to_artist(rb_first(&lib_artist_root))->album_root));
1968 	pos = 0;
1969 	for (i = 0; i < count; i++) {
1970 		struct album_list *a;
1971 
1972 		while (pos < r[i]) {
1973 			struct artist *artist = album->artist;
1974 			if (!rb_next(&album->tree_node)) {
1975 				artist = to_artist(rb_next(&artist->tree_node));
1976 				album = to_album(rb_first(&artist->album_root));
1977 			} else {
1978 				album = to_album(rb_next(&album->tree_node));
1979 			}
1980 			pos++;
1981 		}
1982 		a = xnew(struct album_list, 1);
1983 		a->album = album;
1984 		list_add_rand(&head, &a->node, i);
1985 	}
1986 	free(r);
1987 
1988 	item = head.next;
1989 	do {
1990 		struct list_head *next = item->next;
1991 		struct album_list *a = container_of(item, struct album_list, node);
1992 		struct tree_track *t;
1993 		struct rb_node *tmp;
1994 
1995 		rb_for_each_entry(t, tmp, &a->album->track_root, tree_node)
1996 			play_queue_append(tree_track_info(t), NULL);
1997 		free(a);
1998 		item = next;
1999 	} while (item != &head);
2000 }
2001 
2002 struct track_list {
2003 	struct list_head node;
2004 	const struct simple_track *track;
2005 };
2006 
cmd_tqueue(char * arg)2007 static void cmd_tqueue(char *arg)
2008 {
2009 	LIST_HEAD(head);
2010 	struct list_head *item;
2011 	int count = 1, i, pos;
2012 	int *r;
2013 
2014 	if (arg) {
2015 		long int val;
2016 
2017 		if (str_to_int(arg, &val) || val <= 0) {
2018 			error_msg("argument must be positive integer");
2019 			return;
2020 		}
2021 		count = val;
2022 	}
2023 	if (count > lib_editable.nr_tracks)
2024 		count = lib_editable.nr_tracks;
2025 	if (!count)
2026 		return;
2027 
2028 	r = rand_array(count, lib_editable.nr_tracks);
2029 	item = lib_editable.head.next;
2030 	pos = 0;
2031 	for (i = 0; i < count; i++) {
2032 		struct track_list *t;
2033 
2034 		while (pos < r[i]) {
2035 			item = item->next;
2036 			pos++;
2037 		}
2038 		t = xnew(struct track_list, 1);
2039 		t->track = to_simple_track(item);
2040 		list_add_rand(&head, &t->node, i);
2041 	}
2042 	free(r);
2043 
2044 	item = head.next;
2045 	do {
2046 		struct list_head *next = item->next;
2047 		struct track_list *t = container_of(item, struct track_list, node);
2048 		play_queue_append(t->track->info, NULL);
2049 		free(t);
2050 		item = next;
2051 	} while (item != &head);
2052 }
2053 
2054 /* tab exp {{{
2055  *
2056  * these functions fill tabexp struct, which is resetted beforehand
2057  */
2058 
2059 /* buffer used for tab expansion */
2060 static char expbuf[512];
2061 
filter_directories(const char * name,const struct stat * s)2062 static int filter_directories(const char *name, const struct stat *s)
2063 {
2064 	return S_ISDIR(s->st_mode);
2065 }
2066 
filter_executable_files(const char * name,const struct stat * s)2067 static int filter_executable_files(const char *name, const struct stat *s)
2068 {
2069 	return S_ISREG(s->st_mode) && (s->st_mode & 0111);
2070 }
2071 
filter_any(const char * name,const struct stat * s)2072 static int filter_any(const char *name, const struct stat *s)
2073 {
2074 	return 1;
2075 }
2076 
filter_playable(const char * name,const struct stat * s)2077 static int filter_playable(const char *name, const struct stat *s)
2078 {
2079 	return S_ISDIR(s->st_mode) || cmus_is_playable(name);
2080 }
2081 
filter_playlist(const char * name,const struct stat * s)2082 static int filter_playlist(const char *name, const struct stat *s)
2083 {
2084 	return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
2085 }
2086 
filter_supported(const char * name,const struct stat * s)2087 static int filter_supported(const char *name, const struct stat *s)
2088 {
2089 	return S_ISDIR(s->st_mode) || cmus_is_supported(name);
2090 }
2091 
expand_files(const char * str)2092 static void expand_files(const char *str)
2093 {
2094 	expand_files_and_dirs(str, filter_any);
2095 }
2096 
expand_directories(const char * str)2097 static void expand_directories(const char *str)
2098 {
2099 	expand_files_and_dirs(str, filter_directories);
2100 }
2101 
expand_playable(const char * str)2102 static void expand_playable(const char *str)
2103 {
2104 	expand_files_and_dirs(str, filter_playable);
2105 }
2106 
expand_playlist(const char * str)2107 static void expand_playlist(const char *str)
2108 {
2109 	expand_files_and_dirs(str, filter_playlist);
2110 }
2111 
expand_supported(const char * str)2112 static void expand_supported(const char *str)
2113 {
2114 	expand_files_and_dirs(str, filter_supported);
2115 }
2116 
expand_add(const char * str)2117 static void expand_add(const char *str)
2118 {
2119 	int flag = parse_flags(&str, "lpqQ");
2120 
2121 	if (flag == -1)
2122 		return;
2123 	if (str == NULL)
2124 		str = "";
2125 	expand_supported(str);
2126 
2127 	if (tabexp.head && flag) {
2128 		snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
2129 		free(tabexp.head);
2130 		tabexp.head = xstrdup(expbuf);
2131 	}
2132 }
2133 
expand_program_paths(const char * str)2134 static void expand_program_paths(const char *str)
2135 {
2136 	if (str == NULL)
2137 		str = "";
2138 	if (str[0] == '~' || strchr(str, '/'))
2139 		expand_files(str);
2140 	else
2141 		expand_env_path(str, filter_executable_files);
2142 }
2143 
expand_program_paths_option(const char * str,const char * opt)2144 static void expand_program_paths_option(const char *str, const char *opt)
2145 {
2146 	expand_program_paths(str);
2147 
2148 	if (tabexp.head && opt) {
2149 		snprintf(expbuf, sizeof(expbuf), "%s=%s", opt, tabexp.head);
2150 		free(tabexp.head);
2151 		tabexp.head = xstrdup(expbuf);
2152 	}
2153 }
2154 
expand_load_save(const char * str)2155 static void expand_load_save(const char *str)
2156 {
2157 	int flag = parse_flags(&str, "lp");
2158 
2159 	if (flag == -1)
2160 		return;
2161 	if (str == NULL)
2162 		str = "";
2163 	expand_playlist(str);
2164 
2165 	if (tabexp.head && flag) {
2166 		snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
2167 		free(tabexp.head);
2168 		tabexp.head = xstrdup(expbuf);
2169 	}
2170 }
2171 
expand_key_context(const char * str,const char * force)2172 static void expand_key_context(const char *str, const char *force)
2173 {
2174 	int pos, i, len = strlen(str);
2175 	char **tails;
2176 
2177 	tails = xnew(char *, NR_CTXS);
2178 	pos = 0;
2179 	for (i = 0; key_context_names[i]; i++) {
2180 		int cmp = strncmp(str, key_context_names[i], len);
2181 		if (cmp > 0)
2182 			continue;
2183 		if (cmp < 0)
2184 			break;
2185 		tails[pos++] = xstrdup(key_context_names[i] + len);
2186 	}
2187 
2188 	if (pos == 0) {
2189 		free(tails);
2190 		return;
2191 	}
2192 	if (pos == 1) {
2193 		char *tmp = xstrjoin(tails[0], " ");
2194 		free(tails[0]);
2195 		tails[0] = tmp;
2196 	}
2197 	snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
2198 	tabexp.head = xstrdup(expbuf);
2199 	tabexp.tails = tails;
2200 	tabexp.count = pos;
2201 }
2202 
get_context(const char * str,int len)2203 static int get_context(const char *str, int len)
2204 {
2205 	int i, c = -1, count = 0;
2206 
2207 	for (i = 0; key_context_names[i]; i++) {
2208 		if (strncmp(str, key_context_names[i], len) == 0) {
2209 			if (key_context_names[i][len] == 0) {
2210 				/* exact */
2211 				return i;
2212 			}
2213 			c = i;
2214 			count++;
2215 		}
2216 	}
2217 	if (count == 1)
2218 		return c;
2219 	return -1;
2220 }
2221 
2222 static void expand_command_line(const char *str);
2223 
expand_bind_args(const char * str)2224 static void expand_bind_args(const char *str)
2225 {
2226 	/* :bind context key function
2227 	 *
2228 	 * possible values for str:
2229 	 *   c
2230 	 *   context k
2231 	 *   context key f
2232 	 *
2233 	 * you need to know context before you can expand function
2234 	 */
2235 	/* start and end pointers for context, key and function */
2236 	const char *cs, *ce, *ks, *ke, *fs;
2237 	int i, c, k, count;
2238 	int flag = parse_flags((const char **)&str, "f");
2239 	const char *force = "";
2240 
2241 	if (flag == -1)
2242 		return;
2243 	if (str == NULL)
2244 		str = "";
2245 
2246 	if (flag == 'f')
2247 		force = "-f ";
2248 
2249 	cs = str;
2250 	ce = strchr(cs, ' ');
2251 	if (ce == NULL) {
2252 		expand_key_context(cs, force);
2253 		return;
2254 	}
2255 
2256 	/* context must be expandable */
2257 	c = get_context(cs, ce - cs);
2258 	if (c == -1) {
2259 		/* context is ambiguous or invalid */
2260 		return;
2261 	}
2262 
2263 	ks = ce;
2264 	while (*ks == ' ')
2265 		ks++;
2266 	ke = strchr(ks, ' ');
2267 	if (ke == NULL) {
2268 		/* expand key */
2269 		int len = strlen(ks);
2270 		PTR_ARRAY(array);
2271 
2272 		for (i = 0; key_table[i].name; i++) {
2273 			int cmp = strncmp(ks, key_table[i].name, len);
2274 			if (cmp > 0)
2275 				continue;
2276 			if (cmp < 0)
2277 				break;
2278 			ptr_array_add(&array, xstrdup(key_table[i].name + len));
2279 		}
2280 
2281 		if (!array.count)
2282 			return;
2283 
2284 		if (array.count == 1) {
2285 			char **ptrs = array.ptrs;
2286 			char *tmp = xstrjoin(ptrs[0], " ");
2287 			free(ptrs[0]);
2288 			ptrs[0] = tmp;
2289 		}
2290 
2291 		snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
2292 
2293 		tabexp.head = xstrdup(expbuf);
2294 		tabexp.tails = array.ptrs;
2295 		tabexp.count = array.count;
2296 		return;
2297 	}
2298 
2299 	/* key must be expandable */
2300 	k = -1;
2301 	count = 0;
2302 	for (i = 0; key_table[i].name; i++) {
2303 		if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
2304 			if (key_table[i].name[ke - ks] == 0) {
2305 				/* exact */
2306 				k = i;
2307 				count = 1;
2308 				break;
2309 			}
2310 			k = i;
2311 			count++;
2312 		}
2313 	}
2314 	if (count != 1) {
2315 		/* key is ambiguous or invalid */
2316 		return;
2317 	}
2318 
2319 	fs = ke;
2320 	while (*fs == ' ')
2321 		fs++;
2322 
2323 	if (*fs == ':')
2324 		fs++;
2325 
2326 	/* expand com [arg...] */
2327 	expand_command_line(fs);
2328 	if (tabexp.head == NULL) {
2329 		/* command expand failed */
2330 		return;
2331 	}
2332 
2333 	/*
2334 	 * tabexp.head is now "com"
2335 	 * tabexp.tails is [ mand1 mand2 ... ]
2336 	 *
2337 	 * need to change tabexp.head to "context key com"
2338 	 */
2339 
2340 	snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
2341 			key_table[k].name, tabexp.head);
2342 	free(tabexp.head);
2343 	tabexp.head = xstrdup(expbuf);
2344 }
2345 
expand_unbind_args(const char * str)2346 static void expand_unbind_args(const char *str)
2347 {
2348 	/* :unbind context key */
2349 	/* start and end pointers for context and key */
2350 	const char *cs, *ce, *ks;
2351 	const struct binding *b;
2352 	PTR_ARRAY(array);
2353 	int c, len;
2354 
2355 	cs = str;
2356 	ce = strchr(cs, ' ');
2357 	if (ce == NULL) {
2358 		expand_key_context(cs, "");
2359 		return;
2360 	}
2361 
2362 	/* context must be expandable */
2363 	c = get_context(cs, ce - cs);
2364 	if (c == -1) {
2365 		/* context is ambiguous or invalid */
2366 		return;
2367 	}
2368 
2369 	ks = ce;
2370 	while (*ks == ' ')
2371 		ks++;
2372 
2373 	/* expand key */
2374 	len = strlen(ks);
2375 	b = key_bindings[c];
2376 	while (b) {
2377 		if (!strncmp(ks, b->key->name, len))
2378 			ptr_array_add(&array, xstrdup(b->key->name + len));
2379 		b = b->next;
2380 	}
2381 	if (!array.count)
2382 		return;
2383 
2384 	snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
2385 
2386 	tabexp.head = xstrdup(expbuf);
2387 	tabexp.tails = array.ptrs;
2388 	tabexp.count = array.count;
2389 }
2390 
expand_factivate(const char * str)2391 static void expand_factivate(const char *str)
2392 {
2393 	/* "name1 name2 name3", expand only name3 */
2394 	struct filter_entry *e;
2395 	const char *name;
2396 	PTR_ARRAY(array);
2397 	int str_len, len, i;
2398 
2399 	str_len = strlen(str);
2400 	i = str_len;
2401 	while (i > 0) {
2402 		if (str[i - 1] == ' ')
2403 			break;
2404 		i--;
2405 	}
2406 	len = str_len - i;
2407 	name = str + i;
2408 
2409 	list_for_each_entry(e, &filters_head, node) {
2410 		if (!strncmp(name, e->name, len))
2411 			ptr_array_add(&array, xstrdup(e->name + len));
2412 	}
2413 	if (!array.count)
2414 		return;
2415 
2416 	tabexp.head = xstrdup(str);
2417 	tabexp.tails = array.ptrs;
2418 	tabexp.count = array.count;
2419 }
2420 
expand_fset(const char * str)2421 static void expand_fset(const char *str)
2422 {
2423 	struct filter_entry *e;
2424 	PTR_ARRAY(array);
2425 
2426 	list_for_each_entry(e, &filters_head, node) {
2427 		char *line = xnew(char, strlen(e->name) + strlen(e->filter) + 2);
2428 		sprintf(line, "%s=%s", e->name, e->filter);
2429 		if (!strncmp(str, line, strlen(str)))
2430 			ptr_array_add(&array, xstrdup(line + strlen(str)));
2431 		free(line);
2432 	}
2433 	if (!array.count)
2434 		return;
2435 
2436 	tabexp.head = xstrdup(str);
2437 	tabexp.tails = array.ptrs;
2438 	tabexp.count = array.count;
2439 }
2440 
expand_options(const char * str)2441 static void expand_options(const char *str)
2442 {
2443 	struct cmus_opt *opt;
2444 	int len;
2445 	char **tails, *sep;
2446 
2447 	/* tabexp is resetted */
2448 	len = strlen(str);
2449 	sep = strchr(str, '=');
2450 	if (len > 1 && sep) {
2451 		/* expand value */
2452 		char *var = xstrndup(str, sep - str);
2453 
2454 		list_for_each_entry(opt, &option_head, node) {
2455 			if (strcmp(var, opt->name) == 0) {
2456 				if (str[len - 1] == '=') {
2457 					char buf[OPTION_MAX_SIZE];
2458 
2459 					tails = xnew(char *, 1);
2460 
2461 					buf[0] = 0;
2462 					opt->get(opt->data, buf, OPTION_MAX_SIZE);
2463 					tails[0] = xstrdup(buf);
2464 
2465 					tabexp.head = xstrdup(str);
2466 					tabexp.tails = tails;
2467 					tabexp.count = 1;
2468 				} else if (opt->flags & OPT_PROGRAM_PATH) {
2469 					expand_program_paths_option(sep + 1, var);
2470 				}
2471 				break;
2472 			}
2473 		}
2474 		free(var);
2475 	} else {
2476 		/* expand variable */
2477 		int pos;
2478 
2479 		tails = xnew(char *, nr_options);
2480 		pos = 0;
2481 		list_for_each_entry(opt, &option_head, node) {
2482 			if (strncmp(str, opt->name, len) == 0)
2483 				tails[pos++] = xstrdup(opt->name + len);
2484 		}
2485 		if (pos > 0) {
2486 			if (pos == 1) {
2487 				/* only one variable matches, add '=' */
2488 				char *tmp = xstrjoin(tails[0], "=");
2489 
2490 				free(tails[0]);
2491 				tails[0] = tmp;
2492 			}
2493 
2494 			tabexp.head = xstrdup(str);
2495 			tabexp.tails = tails;
2496 			tabexp.count = pos;
2497 		} else {
2498 			free(tails);
2499 		}
2500 	}
2501 }
2502 
expand_toptions(const char * str)2503 static void expand_toptions(const char *str)
2504 {
2505 	struct cmus_opt *opt;
2506 	int len, pos;
2507 	char **tails;
2508 
2509 	tails = xnew(char *, nr_options);
2510 	len = strlen(str);
2511 	pos = 0;
2512 	list_for_each_entry(opt, &option_head, node) {
2513 		if (opt->toggle == NULL)
2514 			continue;
2515 		if (strncmp(str, opt->name, len) == 0)
2516 			tails[pos++] = xstrdup(opt->name + len);
2517 	}
2518 	if (pos > 0) {
2519 		tabexp.head = xstrdup(str);
2520 		tabexp.tails = tails;
2521 		tabexp.count = pos;
2522 	} else {
2523 		free(tails);
2524 	}
2525 }
2526 
load_themes(const char * dirname,const char * str,struct ptr_array * array)2527 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2528 {
2529 	struct directory dir;
2530 	const char *name, *dot;
2531 	int len = strlen(str);
2532 
2533 	if (dir_open(&dir, dirname))
2534 		return;
2535 
2536 	while ((name = dir_read(&dir))) {
2537 		if (!S_ISREG(dir.st.st_mode))
2538 			continue;
2539 		if (strncmp(name, str, len))
2540 			continue;
2541 		dot = strrchr(name, '.');
2542 		if (dot == NULL || strcmp(dot, ".theme"))
2543 			continue;
2544 		if (dot - name < len)
2545 			/* str is  "foo.th"
2546 			 * matches "foo.theme"
2547 			 * which also ends with ".theme"
2548 			 */
2549 			continue;
2550 		ptr_array_add(array, xstrndup(name + len, dot - name - len));
2551 	}
2552 	dir_close(&dir);
2553 }
2554 
expand_colorscheme(const char * str)2555 static void expand_colorscheme(const char *str)
2556 {
2557 	PTR_ARRAY(array);
2558 
2559 	load_themes(cmus_config_dir, str, &array);
2560 	load_themes(cmus_data_dir, str, &array);
2561 
2562 	if (array.count) {
2563 		ptr_array_sort(&array, strptrcmp);
2564 
2565 		tabexp.head = xstrdup(str);
2566 		tabexp.tails = array.ptrs;
2567 		tabexp.count = array.count;
2568 	}
2569 }
2570 
2571 static void expand_commands(const char *str);
2572 
2573 /* tab exp }}} */
2574 
2575 /* sort by name */
2576 struct command commands[] = {
2577 	{ "add",                   cmd_add,              1, 1,  expand_add,           0, 0          },
2578 	{ "bind",                  cmd_bind,             1, 1,  expand_bind_args,     0, CMD_UNSAFE },
2579 	{ "browser-up",            cmd_browser_up,       0, 0,  NULL,                 0, 0          },
2580 	{ "cd",                    cmd_cd,               0, 1,  expand_directories,   0, 0          },
2581 	{ "clear",                 cmd_clear,            0, 1,  NULL,                 0, 0          },
2582 	{ "colorscheme",           cmd_colorscheme,      1, 1,  expand_colorscheme,   0, 0          },
2583 	{ "echo",                  cmd_echo,             1, -1, NULL,                 0, 0          },
2584 	{ "factivate",             cmd_factivate,        0, 1,  expand_factivate,     0, 0          },
2585 	{ "filter",                cmd_filter,           0, 1,  NULL,                 0, 0          },
2586 	{ "fset",                  cmd_fset,             1, 1,  expand_fset,          0, 0          },
2587 	{ "help",                  cmd_help,             0, 0,  NULL,                 0, 0          },
2588 	{ "invert",                cmd_invert,           0, 0,  NULL,                 0, 0          },
2589 	{ "live-filter",           cmd_live_filter,      0, 1,  NULL,                 0, CMD_LIVE   },
2590 	{ "load",                  cmd_load,             1, 1,  expand_load_save,     0, 0          },
2591 	{ "lqueue",                cmd_lqueue,           0, 1,  NULL,                 0, 0          },
2592 	{ "mark",                  cmd_mark,             0, 1,  NULL,                 0, 0          },
2593 	{ "mute",                  cmd_mute,             0, 0,  NULL,                 0, 0          },
2594 	{ "player-next",           cmd_p_next,           0, 0,  NULL,                 0, 0          },
2595 	{ "player-pause",          cmd_p_pause,          0, 0,  NULL,                 0, 0          },
2596 	{ "player-pause-playback", cmd_p_pause_playback, 0, 0,  NULL,                 0, 0          },
2597 	{ "player-play",           cmd_p_play,           0, 1,  expand_playable,      0, 0          },
2598 	{ "player-prev",           cmd_p_prev,           0, 0,  NULL,                 0, 0          },
2599 	{ "player-stop",           cmd_p_stop,           0, 0,  NULL,                 0, 0          },
2600 	{ "prev-view",             cmd_prev_view,        0, 0,  NULL,                 0, 0          },
2601 	{ "left-view",             cmd_left_view,        0, 0,  NULL,                 0, 0          },
2602 	{ "right-view",            cmd_right_view,       0, 0,  NULL,                 0, 0          },
2603 	{ "pl-create",             cmd_pl_create,        1, -1, NULL,                 0, 0          },
2604 	{ "pl-export",             cmd_pl_export,        1, -1, NULL,                 0, 0          },
2605 	{ "pl-import",             cmd_pl_import,        0, -1, NULL,                 0, 0          },
2606 	{ "pl-rename",             cmd_pl_rename,        1, -1, NULL,                 0, 0          },
2607 	{ "push",                  cmd_push,             0, -1, expand_commands,      0, 0          },
2608 	{ "pwd",                   cmd_pwd,              0, 0,  NULL,                 0, 0          },
2609 	{ "raise-vte",             cmd_raise_vte,        0, 0,  NULL,                 0, 0          },
2610 	{ "rand",                  cmd_rand,             0, 0,  NULL,                 0, 0          },
2611 	{ "quit",                  cmd_quit,             0, 1,  NULL,                 0, 0          },
2612 	{ "refresh",               cmd_refresh,          0, 0,  NULL,                 0, 0          },
2613 	{ "run",                   cmd_run,              1, -1, expand_program_paths, 0, CMD_UNSAFE },
2614 	{ "save",                  cmd_save,             0, 1,  expand_load_save,     0, CMD_UNSAFE },
2615 	{ "search-b-start",        cmd_search_b_start,   0, 0,  NULL,                 0, 0          },
2616 	{ "search-next",           cmd_search_next,      0, 0,  NULL,                 0, 0          },
2617 	{ "search-prev",           cmd_search_prev,      0, 0,  NULL,                 0, 0          },
2618 	{ "search-start",          cmd_search_start,     0, 0,  NULL,                 0, 0          },
2619 	{ "seek",                  cmd_seek,             1, 1,  NULL,                 0, 0          },
2620 	{ "set",                   cmd_set,              1, 1,  expand_options,       0, 0          },
2621 	{ "shell",                 cmd_shell,            1, -1, expand_program_paths, 0, CMD_UNSAFE },
2622 	{ "showbind",              cmd_showbind,         1, 1,  expand_unbind_args,   0, 0          },
2623 	{ "shuffle",               cmd_reshuffle,        0, 0,  NULL,                 0, 0          },
2624 	{ "source",                cmd_source,           1, 1,  expand_files,         0, CMD_UNSAFE },
2625 	{ "toggle",                cmd_toggle,           1, 1,  expand_toptions,      0, 0          },
2626 	{ "tqueue",                cmd_tqueue,           0, 1,  NULL,                 0, 0          },
2627 	{ "unbind",                cmd_unbind,           1, 1,  expand_unbind_args,   0, 0          },
2628 	{ "unmark",                cmd_unmark,           0, 0,  NULL,                 0, 0          },
2629 	{ "update-cache",          cmd_update_cache,     0, 1,  NULL,                 0, 0          },
2630 	{ "version",               cmd_version,          0, 0,  NULL,                 0, 0          },
2631 	{ "view",                  cmd_view,             1, 1,  NULL,                 0, 0          },
2632 	{ "vol",                   cmd_vol,              1, 2,  NULL,                 0, 0          },
2633 	{ "w",                     cmd_save,             0, 1,  expand_load_save,     0, CMD_UNSAFE },
2634 	{ "win-activate",          cmd_win_activate,     0, 0,  NULL,                 0, 0          },
2635 	{ "win-add-l",             cmd_win_add_l,        0, 1,  NULL,                 0, 0          },
2636 	{ "win-add-p",             cmd_win_add_p,        0, 1,  NULL,                 0, 0          },
2637 	{ "win-add-Q",             cmd_win_add_Q,        0, 1,  NULL,                 0, 0          },
2638 	{ "win-add-q",             cmd_win_add_q,        0, 1,  NULL,                 0, 0          },
2639 	{ "win-bottom",            cmd_win_bottom,       0, 0,  NULL,                 0, 0          },
2640 	{ "win-down",              cmd_win_down,         0, 1,  NULL,                 0, 0          },
2641 	{ "win-half-page-down",    cmd_win_hf_pg_down,   0, 0,  NULL,                 0, 0          },
2642 	{ "win-half-page-up",      cmd_win_hf_pg_up,     0, 0,  NULL,                 0, 0          },
2643 	{ "win-mv-after",          cmd_win_mv_after,     0, 0,  NULL,                 0, 0          },
2644 	{ "win-mv-before",         cmd_win_mv_before,    0, 0,  NULL,                 0, 0          },
2645 	{ "win-next",              cmd_win_next,         0, 0,  NULL,                 0, 0          },
2646 	{ "win-page-bottom",       cmd_win_pg_bottom,    0, 0,  NULL,                 0, 0          },
2647 	{ "win-page-down",         cmd_win_pg_down,      0, 0,  NULL,                 0, 0          },
2648 	{ "win-page-middle",       cmd_win_pg_middle,    0, 0,  NULL,                 0, 0          },
2649 	{ "win-page-top",          cmd_win_pg_top,       0, 0,  NULL,                 0, 0          },
2650 	{ "win-page-up",           cmd_win_pg_up,        0, 0,  NULL,                 0, 0          },
2651 	{ "win-remove",            cmd_win_remove,       0, 0,  NULL,                 0, CMD_UNSAFE },
2652 	{ "win-scroll-down",       cmd_win_scroll_down,  0, 0,  NULL,                 0, 0          },
2653 	{ "win-scroll-up",         cmd_win_scroll_up,    0, 0,  NULL,                 0, 0          },
2654 	{ "win-sel-cur",           cmd_win_sel_cur,      0, 0,  NULL,                 0, 0          },
2655 	{ "win-toggle",            cmd_win_toggle,       0, 0,  NULL,                 0, 0          },
2656 	{ "win-top",               cmd_win_top,          0, 0,  NULL,                 0, 0          },
2657 	{ "win-up",                cmd_win_up,           0, 1,  NULL,                 0, 0          },
2658 	{ "win-update",            cmd_win_update,       0, 0,  NULL,                 0, 0          },
2659 	{ "win-update-cache",      cmd_win_update_cache, 0, 1,  NULL,                 0, 0          },
2660 	{ "wq",                    cmd_quit,             0, 1,  NULL,                 0, 0          },
2661 	{ NULL,                    NULL,                 0, 0,  0,                    0, 0          }
2662 };
2663 
2664 /* fills tabexp struct */
expand_commands(const char * str)2665 static void expand_commands(const char *str)
2666 {
2667 	int i, len, pos;
2668 	char **tails;
2669 
2670 	/* tabexp is resetted */
2671 	tails = xnew(char *, N_ELEMENTS(commands) - 1);
2672 	len = strlen(str);
2673 	pos = 0;
2674 	for (i = 0; commands[i].name; i++) {
2675 		if (strncmp(str, commands[i].name, len) == 0)
2676 			tails[pos++] = xstrdup(commands[i].name + len);
2677 	}
2678 	if (pos > 0) {
2679 		if (pos == 1) {
2680 			/* only one command matches, add ' ' */
2681 			char *tmp = xstrjoin(tails[0], " ");
2682 
2683 			free(tails[0]);
2684 			tails[0] = tmp;
2685 		}
2686 		tabexp.head = xstrdup(str);
2687 		tabexp.tails = tails;
2688 		tabexp.count = pos;
2689 	} else {
2690 		free(tails);
2691 	}
2692 }
2693 
get_command(const char * str)2694 struct command *get_command(const char *str)
2695 {
2696 	int i, len;
2697 
2698 	while (*str == ' ')
2699 		str++;
2700 	for (len = 0; str[len] && str[len] != ' '; len++)
2701 		;
2702 
2703 	for (i = 0; commands[i].name; i++) {
2704 		if (strncmp(str, commands[i].name, len))
2705 			continue;
2706 
2707 		if (commands[i].name[len] == 0) {
2708 			/* exact */
2709 			return &commands[i];
2710 		}
2711 
2712 		if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2713 			/* ambiguous */
2714 			return NULL;
2715 		}
2716 		return &commands[i];
2717 	}
2718 	return NULL;
2719 }
2720 
2721 /* fills tabexp struct */
expand_command_line(const char * str)2722 static void expand_command_line(const char *str)
2723 {
2724 	/* :command [arg]...
2725 	 *
2726 	 * examples:
2727 	 *
2728 	 * str      expanded value (tabexp.head)
2729 	 * -------------------------------------
2730 	 *   fs     fset
2731 	 *   b c    bind common
2732 	 *   se     se          (tabexp.tails = [ ek t ])
2733 	 */
2734 	/* command start/end, argument start */
2735 	const char *cs, *ce, *as;
2736 	const struct command *cmd;
2737 
2738 	cs = str;
2739 	ce = strchr(cs, ' ');
2740 	if (ce == NULL) {
2741 		/* expand command */
2742 		expand_commands(cs);
2743 		return;
2744 	}
2745 
2746 	/* command must be expandable */
2747 	cmd = get_command(cs);
2748 	if (cmd == NULL) {
2749 		/* command ambiguous or invalid */
2750 		return;
2751 	}
2752 
2753 	if (cmd->expand == NULL) {
2754 		/* can't expand argument */
2755 		return;
2756 	}
2757 
2758 	as = ce;
2759 	while (*as == ' ')
2760 		as++;
2761 
2762 	/* expand argument */
2763 	cmd->expand(as);
2764 	if (tabexp.head == NULL) {
2765 		/* argument expansion failed */
2766 		return;
2767 	}
2768 
2769 	/* tabexp.head is now start of the argument string */
2770 	snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2771 	free(tabexp.head);
2772 	tabexp.head = xstrdup(expbuf);
2773 }
2774 
tab_expand(int direction)2775 static void tab_expand(int direction)
2776 {
2777 	char *s1, *s2, *tmp;
2778 	int pos;
2779 
2780 	/* strip white space */
2781 	pos = 0;
2782 	while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2783 		pos++;
2784 
2785 	/* string to expand */
2786 	s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2787 
2788 	/* tail */
2789 	s2 = xstrdup(cmdline.line + cmdline.bpos);
2790 
2791 	tmp = tabexp_expand(s1, expand_command_line, direction);
2792 	if (tmp) {
2793 		/* tmp.s2 */
2794 		int l1, l2;
2795 
2796 		l1 = strlen(tmp);
2797 		l2 = strlen(s2);
2798 		cmdline.blen = l1 + l2;
2799 		if (cmdline.blen >= cmdline.size) {
2800 			while (cmdline.blen >= cmdline.size)
2801 				cmdline.size *= 2;
2802 			cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2803 		}
2804 		sprintf(cmdline.line, "%s%s", tmp, s2);
2805 		cmdline.bpos = l1;
2806 		cmdline.cpos = u_strlen_safe(tmp);
2807 		cmdline.clen = u_strlen_safe(cmdline.line);
2808 		free(tmp);
2809 	}
2810 	free(s1);
2811 	free(s2);
2812 }
2813 
reset_tab_expansion(void)2814 static void reset_tab_expansion(void)
2815 {
2816 	tabexp_reset();
2817 	arg_expand_cmd = -1;
2818 }
2819 
cmdline_modified(void)2820 static void cmdline_modified(void)
2821 {
2822 	char *cmd, *arg;
2823 	struct command *c;
2824 
2825 	if (!parse_command(cmdline.line, &cmd, &arg))
2826 		return;
2827 
2828 	c = get_command(cmd);
2829 	if (!c)
2830 		goto end;
2831 
2832 	if (c->flags & CMD_LIVE)
2833 		run_parsed_command(cmd, arg);
2834 
2835 end:
2836 	free(cmd);
2837 	free(arg);
2838 }
2839 
parse_command(const char * buf,char ** cmdp,char ** argp)2840 int parse_command(const char *buf, char **cmdp, char **argp)
2841 {
2842 	int cmd_start, cmd_end, cmd_len;
2843 	int arg_start, arg_end;
2844 	int i;
2845 
2846 	i = 0;
2847 	while (buf[i] && buf[i] == ' ')
2848 		i++;
2849 
2850 	if (buf[i] == '#')
2851 		return 0;
2852 
2853 	cmd_start = i;
2854 	while (buf[i] && buf[i] != ' ')
2855 		i++;
2856 	cmd_end = i;
2857 	while (buf[i] && buf[i] == ' ')
2858 		i++;
2859 	arg_start = i;
2860 	while (buf[i])
2861 		i++;
2862 	arg_end = i;
2863 
2864 	cmd_len = cmd_end - cmd_start;
2865 	if (cmd_len == 0)
2866 		return 0;
2867 
2868 	*cmdp = xstrndup(buf + cmd_start, cmd_len);
2869 	if (arg_start == arg_end) {
2870 		*argp = NULL;
2871 	} else {
2872 		*argp = xstrndup(buf + arg_start, arg_end - arg_start);
2873 	}
2874 	return 1;
2875 }
2876 
2877 int run_only_safe_commands;
2878 
run_parsed_command(char * cmd,char * arg)2879 void run_parsed_command(char *cmd, char *arg)
2880 {
2881 	int cmd_len = strlen(cmd);
2882 	int i = 0;
2883 
2884 	while (1) {
2885 		const struct command *c = &commands[i];
2886 
2887 		if (c->name == NULL) {
2888 			error_msg("unknown command\n");
2889 			break;
2890 		}
2891 		if (strncmp(cmd, c->name, cmd_len) == 0) {
2892 			const char *next = commands[i + 1].name;
2893 			int exact = c->name[cmd_len] == 0;
2894 
2895 			if (!exact && next && strncmp(cmd, next, cmd_len) == 0) {
2896 				error_msg("ambiguous command\n");
2897 				break;
2898 			}
2899 			if (c->min_args > 0 && arg == NULL) {
2900 				error_msg("not enough arguments\n");
2901 				break;
2902 			}
2903 			if (c->max_args == 0 && arg) {
2904 				error_msg("too many arguments\n");
2905 				break;
2906 			}
2907 			if (run_only_safe_commands && (c->flags & CMD_UNSAFE)) {
2908 				if (c->func != cmd_save || !is_stdout_filename(arg)) {
2909 					d_print("trying to execute unsafe command over net\n");
2910 					break;
2911 				}
2912 			}
2913 			c->func(arg);
2914 			break;
2915 		}
2916 		i++;
2917 	}
2918 }
2919 
run_command(const char * buf)2920 void run_command(const char *buf)
2921 {
2922 	char *cmd, *arg;
2923 
2924 	if (!parse_command(buf, &cmd, &arg))
2925 		return;
2926 
2927 	run_parsed_command(cmd, arg);
2928 	free(arg);
2929 	free(cmd);
2930 }
2931 
reset_history_search(void)2932 static void reset_history_search(void)
2933 {
2934 	history_reset_search(&cmd_history);
2935 	free(history_search_text);
2936 	history_search_text = NULL;
2937 }
2938 
backspace(void)2939 static void backspace(void)
2940 {
2941 	if (cmdline.clen > 0) {
2942 		cmdline_backspace();
2943 	} else {
2944 		input_mode = NORMAL_MODE;
2945 	}
2946 }
2947 
command_mode_ch(uchar ch)2948 void command_mode_ch(uchar ch)
2949 {
2950 	switch (ch) {
2951 	case 0x01: // ^A
2952 		cmdline_move_home();
2953 		break;
2954 	case 0x02: // ^B
2955 		cmdline_move_left();
2956 		break;
2957 	case 0x04: // ^D
2958 		cmdline_delete_ch();
2959 		cmdline_modified();
2960 		break;
2961 	case 0x05: // ^E
2962 		cmdline_move_end();
2963 		break;
2964 	case 0x06: // ^F
2965 		cmdline_move_right();
2966 		break;
2967 	case 0x03: // ^C
2968 	case 0x07: // ^G
2969 	case 0x1B: // ESC
2970 		if (cmdline.blen) {
2971 			history_add_line(&cmd_history, cmdline.line);
2972 			cmdline_clear();
2973 		}
2974 		input_mode = NORMAL_MODE;
2975 		break;
2976 	case 0x10: // ^P
2977 		command_mode_key(KEY_UP);
2978 		return;
2979 	case 0xE: // ^N
2980 		command_mode_key(KEY_DOWN);
2981 		return;
2982 	case 0x0A:
2983 		if (cmdline.blen) {
2984 			run_command(cmdline.line);
2985 			history_add_line(&cmd_history, cmdline.line);
2986 			cmdline_clear();
2987 		}
2988 		input_mode = NORMAL_MODE;
2989 		break;
2990 	case 0x0B:
2991 		cmdline_clear_end();
2992 		cmdline_modified();
2993 		break;
2994 	case 0x09:
2995 		tab_expand(1);
2996 		break;
2997 	case 0x15:
2998 		cmdline_backspace_to_bol();
2999 		cmdline_modified();
3000 		break;
3001 	case 0x17: // ^W
3002 		cmdline_backward_delete_word(cmdline_word_delimiters);
3003 		cmdline_modified();
3004 		break;
3005 	case 0x08: // ^H
3006 	case 127:
3007 		backspace();
3008 		cmdline_modified();
3009 		break;
3010 	default:
3011 		cmdline_insert_ch(ch);
3012 		cmdline_modified();
3013 	}
3014 	reset_history_search();
3015 	if (ch != 0x09)
3016 		reset_tab_expansion();
3017 }
3018 
command_mode_escape(int c)3019 void command_mode_escape(int c)
3020 {
3021 	switch (c) {
3022 	case 98:
3023 		cmdline_backward_word(cmdline_filename_delimiters);
3024 		break;
3025 	case 100:
3026 		cmdline_delete_word(cmdline_filename_delimiters);
3027 		cmdline_modified();
3028 		break;
3029 	case 102:
3030 		cmdline_forward_word(cmdline_filename_delimiters);
3031 		break;
3032 	case 127:
3033 	case KEY_BACKSPACE:
3034 		cmdline_backward_delete_word(cmdline_filename_delimiters);
3035 		cmdline_modified();
3036 		break;
3037 	}
3038 	reset_history_search();
3039 }
3040 
command_mode_key(int key)3041 void command_mode_key(int key)
3042 {
3043 	if (key != KEY_BTAB)
3044 		reset_tab_expansion();
3045 	switch (key) {
3046 	case KEY_DC:
3047 		cmdline_delete_ch();
3048 		cmdline_modified();
3049 		break;
3050 	case KEY_BACKSPACE:
3051 		backspace();
3052 		cmdline_modified();
3053 		break;
3054 	case KEY_LEFT:
3055 		cmdline_move_left();
3056 		return;
3057 	case KEY_RIGHT:
3058 		cmdline_move_right();
3059 		return;
3060 	case KEY_HOME:
3061 		cmdline_move_home();
3062 		return;
3063 	case KEY_END:
3064 		cmdline_move_end();
3065 		return;
3066 	case KEY_UP:
3067 		{
3068 			const char *s;
3069 
3070 			if (history_search_text == NULL)
3071 				history_search_text = xstrdup(cmdline.line);
3072 			s = history_search_forward(&cmd_history, history_search_text);
3073 			if (s)
3074 				cmdline_set_text(s);
3075 		}
3076 		return;
3077 	case KEY_DOWN:
3078 		if (history_search_text) {
3079 			const char *s;
3080 
3081 			s = history_search_backward(&cmd_history, history_search_text);
3082 			if (s) {
3083 				cmdline_set_text(s);
3084 			} else {
3085 				cmdline_set_text(history_search_text);
3086 			}
3087 		}
3088 		return;
3089 	case KEY_BTAB:
3090 		tab_expand(-1);
3091 		break;
3092 	default:
3093 		d_print("key = %c (%d)\n", key, key);
3094 	}
3095 	reset_history_search();
3096 }
3097 
command_mode_mouse(MEVENT * event)3098 void command_mode_mouse(MEVENT *event)
3099 {
3100 	if ((event->bstate & BUTTON1_PRESSED) || (event->bstate & BUTTON3_PRESSED)) {
3101 		if (event->y <= window_get_nr_rows(current_win()) + 2) {
3102 			if (cmdline.blen) {
3103 				history_add_line(&cmd_history, cmdline.line);
3104 				cmdline_clear();
3105 			}
3106 			input_mode = NORMAL_MODE;
3107 			normal_mode_mouse(event);
3108 			return;
3109 		}
3110 		if (event->x == 0)
3111 			return;
3112 		int i = event->x > cmdline.clen ? cmdline.clen : event->x - 1;
3113 		while (i < cmdline.cpos)
3114 			cmdline_move_left();
3115 		while (i > cmdline.cpos)
3116 			cmdline_move_right();
3117 	} else if (event->bstate & BUTTON4_PRESSED) {
3118 		command_mode_key(KEY_UP);
3119 	} else if (event->bstate & BUTTON5_PRESSED) {
3120 		command_mode_key(KEY_DOWN);
3121 	}
3122 }
3123 
commands_init(void)3124 void commands_init(void)
3125 {
3126 	cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
3127 	history_load(&cmd_history, cmd_history_filename, 2000);
3128 }
3129 
commands_exit(void)3130 void commands_exit(void)
3131 {
3132 	view_clear(TREE_VIEW);
3133 	view_clear(SORTED_VIEW);
3134 	view_clear(PLAYLIST_VIEW);
3135 	view_clear(QUEUE_VIEW);
3136 	history_save(&cmd_history);
3137 	history_free(&cmd_history);
3138 	free(cmd_history_filename);
3139 	tabexp_reset();
3140 }
3141