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