Handle streams separately in tree_add_track()
[cmus.git] / command_mode.c
blob396997a5c839084d11e61bfe544a1c1f12deba9e
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "command_mode.h"
21 #include "search_mode.h"
22 #include "cmdline.h"
23 #include "options.h"
24 #include "ui_curses.h"
25 #include "history.h"
26 #include "tabexp.h"
27 #include "tabexp_file.h"
28 #include "browser.h"
29 #include "filters.h"
30 #include "player.h"
31 #include "output.h"
32 #include "editable.h"
33 #include "lib.h"
34 #include "pl.h"
35 #include "play_queue.h"
36 #include "cmus.h"
37 #include "worker.h"
38 #include "keys.h"
39 #include "xmalloc.h"
40 #include "xstrjoin.h"
41 #include "misc.h"
42 #include "path.h"
43 #include "format_print.h"
44 #include "spawn.h"
45 #include "utils.h"
46 #include "list.h"
47 #include "debug.h"
48 #include "load_dir.h"
49 #include "config/datadir.h"
50 #include "help.h"
52 #include <stdlib.h>
53 #include <ctype.h>
54 #include <sys/types.h>
55 #include <sys/wait.h>
56 #include <dirent.h>
57 #include <pwd.h>
59 #if defined(__sun__)
60 #include <ncurses.h>
61 #else
62 #include <curses.h>
63 #endif
65 static struct history cmd_history;
66 static char *cmd_history_filename;
67 static char *history_search_text = NULL;
68 static int arg_expand_cmd = -1;
69 static int prev_view = -1;
71 static char *get_home_dir(const char *username)
73 struct passwd *passwd;
75 if (username == NULL)
76 return xstrdup(home_dir);
77 passwd = getpwnam(username);
78 if (passwd == NULL)
79 return NULL;
80 /* don't free passwd */
81 return xstrdup(passwd->pw_dir);
84 static char *expand_filename(const char *name)
86 if (name[0] == '~') {
87 char *slash;
89 slash = strchr(name, '/');
90 if (slash) {
91 char *username, *home;
93 if (slash - name - 1 > 0) {
94 /* ~user/... */
95 username = xstrndup(name + 1, slash - name - 1);
96 } else {
97 /* ~/... */
98 username = NULL;
100 home = get_home_dir(username);
101 free(username);
102 if (home) {
103 char *expanded;
105 expanded = xstrjoin(home, slash);
106 free(home);
107 return expanded;
108 } else {
109 return xstrdup(name);
111 } else {
112 if (name[1] == 0) {
113 return xstrdup(home_dir);
114 } else {
115 char *home;
117 home = get_home_dir(name + 1);
118 if (home)
119 return home;
120 return xstrdup(name);
123 } else {
124 return xstrdup(name);
128 /* view {{{ */
130 void view_clear(int view)
132 switch (view) {
133 case TREE_VIEW:
134 case SORTED_VIEW:
135 worker_remove_jobs(JOB_TYPE_LIB);
136 editable_lock();
137 editable_clear(&lib_editable);
139 /* FIXME: make this optional? */
140 lib_clear_store();
142 editable_unlock();
143 break;
144 case PLAYLIST_VIEW:
145 worker_remove_jobs(JOB_TYPE_PL);
146 editable_lock();
147 editable_clear(&pl_editable);
148 editable_unlock();
149 break;
150 case QUEUE_VIEW:
151 worker_remove_jobs(JOB_TYPE_QUEUE);
152 editable_lock();
153 editable_clear(&pq_editable);
154 editable_unlock();
155 break;
156 default:
157 info_msg(":clear only works in views 1-4");
161 void view_add(int view, char *arg, int prepend)
163 char *tmp, *name;
164 enum file_type ft;
166 tmp = expand_filename(arg);
167 ft = cmus_detect_ft(tmp, &name);
168 if (ft == FILE_TYPE_INVALID) {
169 error_msg("adding '%s': %s", tmp, strerror(errno));
170 free(tmp);
171 return;
173 free(tmp);
175 switch (view) {
176 case TREE_VIEW:
177 case SORTED_VIEW:
178 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
179 break;
180 case PLAYLIST_VIEW:
181 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
182 break;
183 case QUEUE_VIEW:
184 if (prepend) {
185 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
186 } else {
187 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
189 break;
190 default:
191 info_msg(":add only works in views 1-4");
193 free(name);
196 void view_load(int view, char *arg)
198 char *tmp, *name;
199 enum file_type ft;
201 tmp = expand_filename(arg);
202 ft = cmus_detect_ft(tmp, &name);
203 if (ft == FILE_TYPE_INVALID) {
204 error_msg("loading '%s': %s", tmp, strerror(errno));
205 free(tmp);
206 return;
208 free(tmp);
210 if (ft == FILE_TYPE_FILE)
211 ft = FILE_TYPE_PL;
212 if (ft != FILE_TYPE_PL) {
213 error_msg("loading '%s': not a playlist file", name);
214 free(name);
215 return;
218 switch (view) {
219 case TREE_VIEW:
220 case SORTED_VIEW:
221 worker_remove_jobs(JOB_TYPE_LIB);
222 editable_lock();
223 editable_clear(&lib_editable);
224 editable_unlock();
225 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
226 free(lib_filename);
227 lib_filename = name;
228 break;
229 case PLAYLIST_VIEW:
230 worker_remove_jobs(JOB_TYPE_PL);
231 editable_lock();
232 editable_clear(&pl_editable);
233 editable_unlock();
234 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
235 free(pl_filename);
236 pl_filename = name;
237 break;
238 default:
239 info_msg(":load only works in views 1-3");
240 free(name);
244 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
246 char *filename = *filenamep;
248 if (arg) {
249 free(filename);
250 filename = xstrdup(arg);
251 *filenamep = filename;
254 editable_lock();
255 if (cmus_save(for_each_ti, filename) == -1)
256 error_msg("saving '%s': %s", filename, strerror(errno));
257 editable_unlock();
260 void view_save(int view, char *arg)
262 if (arg) {
263 char *tmp;
265 tmp = expand_filename(arg);
266 arg = path_absolute(tmp);
267 free(tmp);
270 switch (view) {
271 case TREE_VIEW:
272 case SORTED_VIEW:
273 if (worker_has_job(JOB_TYPE_LIB))
274 goto worker_running;
275 do_save(lib_for_each, arg, &lib_filename);
276 break;
277 case PLAYLIST_VIEW:
278 if (worker_has_job(JOB_TYPE_PL))
279 goto worker_running;
280 do_save(pl_for_each, arg, &pl_filename);
281 break;
282 default:
283 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
285 free(arg);
286 return;
287 worker_running:
288 error_msg("can't save when tracks are being added");
289 free(arg);
292 /* }}} */
294 /* only returns the last flag which is enough for it's callers */
295 static int parse_flags(const char **strp, const char *flags)
297 const char *str = *strp;
298 int flag = 0;
300 if (str == NULL)
301 return flag;
303 while (*str) {
304 if (*str != '-')
305 break;
307 // "-"
308 if (str[1] == 0)
309 break;
311 // "--" or "-- "
312 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
313 str += 2;
314 break;
317 // not "-?" or "-? "
318 if (str[2] && str[2] != ' ')
319 break;
321 flag = str[1];
322 if (!strchr(flags, flag)) {
323 error_msg("invalid option -%c", flag);
324 return -1;
327 str += 2;
329 while (*str == ' ')
330 str++;
332 while (*str == ' ')
333 str++;
334 if (*str == 0)
335 str = NULL;
336 *strp = str;
337 return flag;
340 static int flag_to_view(int flag)
342 switch (flag) {
343 case 'l':
344 return TREE_VIEW;
345 case 'p':
346 return PLAYLIST_VIEW;
347 case 'q':
348 case 'Q':
349 return QUEUE_VIEW;
350 default:
351 return cur_view;
355 static void cmd_add(char *arg)
357 int flag = parse_flags((const char **)&arg, "lpqQ");
359 if (flag == -1)
360 return;
361 if (arg == NULL) {
362 error_msg("not enough arguments\n");
363 return;
365 view_add(flag_to_view(flag), arg, flag == 'Q');
368 static void cmd_clear(char *arg)
370 int flag = parse_flags((const char **)&arg, "lpq");
372 if (flag == -1)
373 return;
374 if (arg) {
375 error_msg("too many arguments\n");
376 return;
378 view_clear(flag_to_view(flag));
381 static void cmd_load(char *arg)
383 int flag = parse_flags((const char **)&arg, "lp");
385 if (flag == -1)
386 return;
387 if (arg == NULL) {
388 error_msg("not enough arguments\n");
389 return;
391 view_load(flag_to_view(flag), arg);
394 static void cmd_save(char *arg)
396 int flag = parse_flags((const char **)&arg, "lp");
398 if (flag == -1)
399 return;
400 view_save(flag_to_view(flag), arg);
403 static void cmd_set(char *arg)
405 char *value = NULL;
406 int i;
408 for (i = 0; arg[i]; i++) {
409 if (arg[i] == '=') {
410 arg[i] = 0;
411 value = &arg[i + 1];
412 break;
415 if (value) {
416 option_set(arg, value);
417 help_win->changed = 1;
418 } else {
419 struct cmus_opt *opt;
420 char buf[OPTION_MAX_SIZE];
422 /* support "set <option>?" */
423 i--;
424 if (arg[i] == '?')
425 arg[i] = 0;
427 opt = option_find(arg);
428 if (opt) {
429 opt->get(opt->id, buf);
430 info_msg("setting: '%s=%s'", arg, buf);
435 static void cmd_toggle(char *arg)
437 struct cmus_opt *opt = option_find(arg);
439 if (opt == NULL)
440 return;
442 if (opt->toggle == NULL) {
443 error_msg("%s is not toggle option", opt->name);
444 return;
446 opt->toggle(opt->id);
447 help_win->changed = 1;
450 static int get_number(char *str, char **end)
452 int val = 0;
454 while (*str >= '0' && *str <= '9') {
455 val *= 10;
456 val += *str++ - '0';
458 *end = str;
459 return val;
462 static void cmd_seek(char *arg)
464 int relative = 0;
465 int seek = 0, sign = 1, count;
467 switch (*arg) {
468 case '-':
469 sign = -1;
470 case '+':
471 relative = 1;
472 arg++;
473 break;
476 count = 0;
477 goto inside;
479 do {
480 int num;
481 char *end;
483 if (*arg != ':')
484 break;
485 arg++;
486 inside:
487 num = get_number(arg, &end);
488 if (arg == end)
489 break;
490 arg = end;
491 seek = seek * 60 + num;
492 } while (++count < 3);
494 seek *= sign;
495 if (!count)
496 goto err;
498 if (count == 1) {
499 switch (tolower(*arg)) {
500 case 'h':
501 seek *= 60;
502 case 'm':
503 seek *= 60;
504 case 's':
505 arg++;
506 break;
510 if (!*arg) {
511 player_seek(seek, relative);
512 return;
514 err:
515 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
518 static void cmd_factivate(char *arg)
520 editable_lock();
521 filters_activate_names(arg);
522 editable_unlock();
525 static void cmd_filter(char *arg)
527 editable_lock();
528 filters_set_anonymous(arg);
529 editable_unlock();
532 static void cmd_fset(char *arg)
534 filters_set_filter(arg);
537 static void cmd_invert(char *arg)
539 editable_lock();
540 switch (cur_view) {
541 case SORTED_VIEW:
542 editable_invert_marks(&lib_editable);
543 break;
544 case PLAYLIST_VIEW:
545 editable_invert_marks(&pl_editable);
546 break;
547 case QUEUE_VIEW:
548 editable_invert_marks(&pq_editable);
549 break;
550 default:
551 info_msg(":invert only works in views 2-4");
553 editable_unlock();
556 static void cmd_mark(char *arg)
558 editable_lock();
559 switch (cur_view) {
560 case SORTED_VIEW:
561 editable_mark(&lib_editable, arg);
562 break;
563 case PLAYLIST_VIEW:
564 editable_mark(&pl_editable, arg);
565 break;
566 case QUEUE_VIEW:
567 editable_mark(&pq_editable, arg);
568 break;
569 default:
570 info_msg(":mark only works in views 2-4");
572 editable_unlock();
575 static void cmd_unmark(char *arg)
577 editable_lock();
578 switch (cur_view) {
579 case SORTED_VIEW:
580 editable_unmark(&lib_editable);
581 break;
582 case PLAYLIST_VIEW:
583 editable_unmark(&pl_editable);
584 break;
585 case QUEUE_VIEW:
586 editable_unmark(&pq_editable);
587 break;
588 default:
589 info_msg(":unmark only works in views 2-4");
591 editable_unlock();
594 static void cmd_update_cache(char *arg)
596 cmus_update_cache();
599 static void cmd_cd(char *arg)
601 if (arg) {
602 char *dir, *absolute;
604 dir = expand_filename(arg);
605 absolute = path_absolute(dir);
606 if (chdir(dir) == -1) {
607 error_msg("could not cd to '%s': %s", dir, strerror(errno));
608 } else {
609 browser_chdir(absolute);
611 free(absolute);
612 free(dir);
613 } else {
614 if (chdir(home_dir) == -1) {
615 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
616 } else {
617 browser_chdir(home_dir);
622 static void cmd_bind(char *arg)
624 int flag = parse_flags((const char **)&arg, "f");
625 char *key, *func;
627 if (flag == -1)
628 return;
630 if (arg == NULL)
631 goto err;
633 key = strchr(arg, ' ');
634 if (key == NULL)
635 goto err;
636 *key++ = 0;
637 while (*key == ' ')
638 key++;
640 func = strchr(key, ' ');
641 if (func == NULL)
642 goto err;
643 *func++ = 0;
644 while (*func == ' ')
645 func++;
646 if (*func == 0)
647 goto err;
649 key_bind(arg, key, func, flag == 'f');
650 return;
651 err:
652 error_msg("expecting 3 arguments (context, key and function)\n");
655 static void cmd_unbind(char *arg)
657 int flag = parse_flags((const char **)&arg, "f");
658 char *key;
660 if (flag == -1)
661 return;
663 if (arg == NULL)
664 goto err;
666 key = strchr(arg, ' ');
667 if (key == NULL)
668 goto err;
669 *key++ = 0;
670 while (*key == ' ')
671 key++;
672 if (*key == 0)
673 goto err;
675 /* FIXME: remove spaces at end */
677 key_unbind(arg, key, flag == 'f');
678 return;
679 err:
680 error_msg("expecting 2 arguments (context and key)\n");
683 static void cmd_showbind(char *arg)
685 char *key;
687 key = strchr(arg, ' ');
688 if (key == NULL)
689 goto err;
690 *key++ = 0;
691 while (*key == ' ')
692 key++;
693 if (*key == 0)
694 goto err;
696 /* FIXME: remove spaces at end */
698 show_binding(arg, key);
699 return;
700 err:
701 error_msg("expecting 2 arguments (context and key)\n");
704 static void cmd_quit(char *arg)
706 if (!worker_has_job(JOB_TYPE_ANY) || yes_no_query("Tracks are being added. Quit and truncate playlist(s)? [y/N]"))
707 cmus_running = 0;
710 static void cmd_reshuffle(char *arg)
712 editable_lock();
713 lib_reshuffle();
714 pl_reshuffle();
715 editable_unlock();
718 static void cmd_source(char *arg)
720 char *filename = expand_filename(arg);
722 if (source_file(filename) == -1)
723 error_msg("sourcing %s: %s", filename, strerror(errno));
724 free(filename);
727 static void cmd_colorscheme(char *arg)
729 char filename[512];
731 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
732 if (source_file(filename) == -1) {
733 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
734 if (source_file(filename) == -1)
735 error_msg("sourcing %s: %s", filename, strerror(errno));
740 * \" inside double-quotes becomes "
741 * \\ inside double-quotes becomes \
743 static char *parse_quoted(const char **strp)
745 const char *str = *strp;
746 const char *start;
747 char *ret, *dst;
749 str++;
750 start = str;
751 while (1) {
752 int c = *str++;
754 if (c == 0)
755 goto error;
756 if (c == '"')
757 break;
758 if (c == '\\') {
759 if (*str++ == 0)
760 goto error;
763 *strp = str;
764 ret = xnew(char, str - start);
765 str = start;
766 dst = ret;
767 while (1) {
768 int c = *str++;
770 if (c == '"')
771 break;
772 if (c == '\\') {
773 c = *str++;
774 if (c != '"' && c != '\\')
775 *dst++ = '\\';
777 *dst++ = c;
779 *dst = 0;
780 return ret;
781 error:
782 error_msg("`\"' expected");
783 return NULL;
786 static char *parse_escaped(const char **strp)
788 const char *str = *strp;
789 const char *start;
790 char *ret, *dst;
792 start = str;
793 while (1) {
794 int c = *str;
796 if (c == 0 || c == ' ' || c == '\'' || c == '"')
797 break;
799 str++;
800 if (c == '\\') {
801 c = *str;
802 if (c == 0)
803 break;
804 str++;
807 *strp = str;
808 ret = xnew(char, str - start + 1);
809 str = start;
810 dst = ret;
811 while (1) {
812 int c = *str;
814 if (c == 0 || c == ' ' || c == '\'' || c == '"')
815 break;
817 str++;
818 if (c == '\\') {
819 c = *str;
820 if (c == 0) {
821 *dst++ = '\\';
822 break;
824 str++;
826 *dst++ = c;
828 *dst = 0;
829 return ret;
832 static char *parse_one(const char **strp)
834 const char *str = *strp;
835 char *ret = NULL;
837 while (1) {
838 char *part;
839 int c = *str;
841 if (!c || c == ' ')
842 break;
843 if (c == '"') {
844 part = parse_quoted(&str);
845 if (part == NULL)
846 goto error;
847 } else if (c == '\'') {
848 /* backslashes are normal chars inside single-quotes */
849 const char *end;
851 str++;
852 end = strchr(str, '\'');
853 if (end == NULL)
854 goto sq_missing;
855 part = xstrndup(str, end - str);
856 str = end + 1;
857 } else {
858 part = parse_escaped(&str);
861 if (ret == NULL) {
862 ret = part;
863 } else {
864 char *tmp = xstrjoin(ret, part);
865 free(ret);
866 ret = tmp;
869 *strp = str;
870 return ret;
871 sq_missing:
872 error_msg("`'' expected");
873 error:
874 free(ret);
875 return NULL;
878 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
880 char **av = NULL;
881 int nr = 0;
882 int alloc = 0;
884 while (*cmd) {
885 char *arg;
887 /* there can't be spaces at start of command
888 * and there is at least one argument */
889 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
890 /* {} is replaced with file arguments */
891 if (*args_idx != -1)
892 goto only_once_please;
893 *args_idx = nr;
894 cmd += 2;
895 goto skip_spaces;
896 } else {
897 arg = parse_one(&cmd);
898 if (arg == NULL)
899 goto error;
902 if (nr == alloc) {
903 alloc = alloc ? alloc * 2 : 4;
904 av = xrenew(char *, av, alloc + 1);
906 av[nr++] = arg;
907 skip_spaces:
908 while (*cmd == ' ')
909 cmd++;
911 av[nr] = NULL;
912 *ac = nr;
913 return av;
914 only_once_please:
915 error_msg("{} can be used only once");
916 error:
917 while (nr > 0)
918 free(av[--nr]);
919 free(av);
920 return NULL;
923 static struct track_info **sel_tis;
924 static int sel_tis_alloc;
925 static int sel_tis_nr;
927 static int add_ti(void *data, struct track_info *ti)
929 if (sel_tis_nr == sel_tis_alloc) {
930 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
931 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
933 track_info_ref(ti);
934 sel_tis[sel_tis_nr++] = ti;
935 return 0;
938 static void cmd_run(char *arg)
940 char **av, **argv;
941 int ac, argc, i, run, files_idx = -1;
943 if (cur_view > QUEUE_VIEW) {
944 info_msg("Command execution is supported only in views 1-4");
945 return;
948 av = parse_cmd(arg, &files_idx, &ac);
949 if (av == NULL) {
950 return;
953 /* collect selected files (struct track_info) */
954 sel_tis = NULL;
955 sel_tis_alloc = 0;
956 sel_tis_nr = 0;
958 editable_lock();
959 switch (cur_view) {
960 case TREE_VIEW:
961 __tree_for_each_sel(add_ti, NULL, 0);
962 break;
963 case SORTED_VIEW:
964 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
965 break;
966 case PLAYLIST_VIEW:
967 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
968 break;
969 case QUEUE_VIEW:
970 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
971 break;
973 editable_unlock();
975 if (sel_tis_nr == 0) {
976 /* no files selected, do nothing */
977 free_str_array(av);
978 return;
980 sel_tis[sel_tis_nr] = NULL;
982 /* build argv */
983 argv = xnew(char *, ac + sel_tis_nr + 1);
984 argc = 0;
985 if (files_idx == -1) {
986 /* add selected files after rest of the args */
987 for (i = 0; i < ac; i++)
988 argv[argc++] = av[i];
989 for (i = 0; i < sel_tis_nr; i++)
990 argv[argc++] = sel_tis[i]->filename;
991 } else {
992 for (i = 0; i < files_idx; i++)
993 argv[argc++] = av[i];
994 for (i = 0; i < sel_tis_nr; i++)
995 argv[argc++] = sel_tis[i]->filename;
996 for (i = files_idx; i < ac; i++)
997 argv[argc++] = av[i];
999 argv[argc] = NULL;
1001 for (i = 0; argv[i]; i++)
1002 d_print("ARG: '%s'\n", argv[i]);
1004 run = 1;
1005 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
1006 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
1007 info_msg("Aborted");
1008 run = 0;
1011 if (run) {
1012 int status;
1014 if (spawn(argv, &status)) {
1015 error_msg("executing %s: %s", argv[0], strerror(errno));
1016 } else {
1017 if (WIFEXITED(status)) {
1018 int rc = WEXITSTATUS(status);
1020 if (rc)
1021 error_msg("%s returned %d", argv[0], rc);
1023 if (WIFSIGNALED(status))
1024 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1026 switch (cur_view) {
1027 case TREE_VIEW:
1028 case SORTED_VIEW:
1029 /* this must be done before sel_tis are unreffed */
1030 free_str_array(av);
1031 free(argv);
1033 /* remove non-existed files, update tags for changed files */
1034 cmus_update_tis(sel_tis, sel_tis_nr);
1036 /* we don't own sel_tis anymore! */
1037 return;
1041 free_str_array(av);
1042 free(argv);
1043 for (i = 0; sel_tis[i]; i++)
1044 track_info_unref(sel_tis[i]);
1045 free(sel_tis);
1048 static int get_one_ti(void *data, struct track_info *ti)
1050 struct track_info **sel_ti = data;
1052 track_info_ref(ti);
1053 *sel_ti = ti;
1054 /* stop the for each loop, we need only the first selected track */
1055 return 1;
1058 static void cmd_echo(char *arg)
1060 struct track_info *sel_ti;
1061 char *ptr = arg;
1063 while (1) {
1064 ptr = strchr(ptr, '{');
1065 if (ptr == NULL)
1066 break;
1067 if (ptr[1] == '}')
1068 break;
1069 ptr++;
1072 if (ptr == NULL) {
1073 info_msg("%s", arg);
1074 return;
1077 if (cur_view > QUEUE_VIEW) {
1078 info_msg("echo with {} in its arguments is supported only in views 1-4");
1079 return;
1082 *ptr = 0;
1083 ptr += 2;
1085 /* get only the first selected track */
1086 sel_ti = NULL;
1088 editable_lock();
1089 switch (cur_view) {
1090 case TREE_VIEW:
1091 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1092 break;
1093 case SORTED_VIEW:
1094 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1095 break;
1096 case PLAYLIST_VIEW:
1097 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1098 break;
1099 case QUEUE_VIEW:
1100 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1101 break;
1103 editable_unlock();
1105 if (sel_ti == NULL)
1106 return;
1108 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1109 track_info_unref(sel_ti);
1112 #define VF_RELATIVE 0x01
1113 #define VF_PERCENTAGE 0x02
1115 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1117 unsigned int f = 0;
1118 int ch, val = 0, digits = 0, sign = 1;
1120 if (*arg == '-') {
1121 arg++;
1122 f |= VF_RELATIVE;
1123 sign = -1;
1124 } else if (*arg == '+') {
1125 arg++;
1126 f |= VF_RELATIVE;
1129 while (1) {
1130 ch = *arg++;
1131 if (ch < '0' || ch > '9')
1132 break;
1133 val *= 10;
1134 val += ch - '0';
1135 digits++;
1137 if (digits == 0)
1138 goto err;
1140 if (ch == '%') {
1141 f |= VF_PERCENTAGE;
1142 ch = *arg;
1144 if (ch)
1145 goto err;
1147 *value = sign * val;
1148 *flags = f;
1149 return 0;
1150 err:
1151 return -1;
1154 static int calc_vol(int val, int old, int max_vol, unsigned int flags)
1156 if (flags & VF_RELATIVE) {
1157 if (flags & VF_PERCENTAGE)
1158 val = scale_from_percentage(val, max_vol);
1159 val += old;
1160 } else if (flags & VF_PERCENTAGE) {
1161 val = scale_from_percentage(val, max_vol);
1163 return clamp(val, 0, max_vol);
1167 * :vol value [value]
1169 * where value is [-+]?[0-9]+%?
1171 static void cmd_vol(char *arg)
1173 char **values = get_words(arg);
1174 unsigned int lf, rf;
1175 int l, r;
1177 if (values[1] && values[2])
1178 goto err;
1180 if (parse_vol_arg(values[0], &l, &lf))
1181 goto err;
1183 r = l;
1184 rf = lf;
1185 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1186 goto err;
1188 free_str_array(values);
1190 if (soft_vol) {
1191 l = calc_vol(l, soft_vol_l, 100, lf);
1192 r = calc_vol(r, soft_vol_r, 100, rf);
1193 player_set_soft_volume(l, r);
1194 } else {
1195 mixer_read_volume();
1196 l = calc_vol(l, volume_l, volume_max, lf);
1197 r = calc_vol(r, volume_r, volume_max, rf);
1198 mixer_set_volume(l, r);
1200 update_statusline();
1201 return;
1202 err:
1203 free_str_array(values);
1204 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1207 static void cmd_prev_view(char *arg)
1209 int tmp;
1210 if (prev_view >= 0) {
1211 tmp = cur_view;
1212 set_view(prev_view);
1213 prev_view = tmp;
1217 static void cmd_view(char *arg)
1219 int view;
1221 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view) && (view - 1) != cur_view) {
1222 prev_view = cur_view;
1223 set_view(view - 1);
1227 static void cmd_p_next(char *arg)
1229 cmus_next();
1232 static void cmd_p_pause(char *arg)
1234 player_pause();
1237 static void cmd_p_play(char *arg)
1239 if (arg) {
1240 cmus_play_file(arg);
1241 } else {
1242 player_play();
1246 static void cmd_p_prev(char *arg)
1248 cmus_prev();
1251 static void cmd_p_stop(char *arg)
1253 player_stop();
1256 static void cmd_search_next(char *arg)
1258 if (search_str) {
1259 if (!search_next(searchable, search_str, search_direction))
1260 search_not_found();
1264 static void cmd_search_prev(char *arg)
1266 if (search_str) {
1267 if (!search_next(searchable, search_str, !search_direction))
1268 search_not_found();
1272 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1274 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1277 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1279 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1282 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1284 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1287 static for_each_sel_ti_cb view_for_each_sel[4] = {
1288 tree_for_each_sel,
1289 sorted_for_each_sel,
1290 pl_for_each_sel,
1291 pq_for_each_sel
1294 /* wrapper for void lib_add_track(struct track_info *) etc. */
1295 static int wrapper_cb(void *data, struct track_info *ti)
1297 add_ti_cb add = data;
1299 add(ti);
1300 return 0;
1303 static void add_from_browser(add_ti_cb add, int job_type)
1305 char *sel = browser_get_sel();
1307 if (sel) {
1308 enum file_type ft;
1309 char *ret;
1311 ft = cmus_detect_ft(sel, &ret);
1312 if (ft != FILE_TYPE_INVALID) {
1313 cmus_add(add, ret, ft, job_type);
1314 window_down(browser_win, 1);
1316 free(ret);
1317 free(sel);
1321 static void cmd_win_add_l(char *arg)
1323 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1324 return;
1326 if (cur_view <= QUEUE_VIEW) {
1327 editable_lock();
1328 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1329 editable_unlock();
1330 } else if (cur_view == BROWSER_VIEW) {
1331 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1335 static void cmd_win_add_p(char *arg)
1337 /* could allow adding dups? */
1338 if (cur_view == PLAYLIST_VIEW)
1339 return;
1341 if (cur_view <= QUEUE_VIEW) {
1342 editable_lock();
1343 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1344 editable_unlock();
1345 } else if (cur_view == BROWSER_VIEW) {
1346 add_from_browser(pl_add_track, JOB_TYPE_PL);
1350 static void cmd_win_add_Q(char *arg)
1352 if (cur_view == QUEUE_VIEW)
1353 return;
1355 if (cur_view <= QUEUE_VIEW) {
1356 editable_lock();
1357 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1358 editable_unlock();
1359 } else if (cur_view == BROWSER_VIEW) {
1360 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1364 static void cmd_win_add_q(char *arg)
1366 if (cur_view == QUEUE_VIEW)
1367 return;
1369 if (cur_view <= QUEUE_VIEW) {
1370 editable_lock();
1371 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1372 editable_unlock();
1373 } else if (cur_view == BROWSER_VIEW) {
1374 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1378 static void cmd_win_activate(char *arg)
1380 struct track_info *info = NULL;
1382 editable_lock();
1383 switch (cur_view) {
1384 case TREE_VIEW:
1385 info = tree_set_selected();
1386 break;
1387 case SORTED_VIEW:
1388 info = sorted_set_selected();
1389 break;
1390 case PLAYLIST_VIEW:
1391 info = pl_set_selected();
1392 break;
1393 case QUEUE_VIEW:
1394 break;
1395 case BROWSER_VIEW:
1396 browser_enter();
1397 break;
1398 case FILTERS_VIEW:
1399 filters_activate();
1400 break;
1401 case HELP_VIEW:
1402 help_select();
1403 break;
1405 editable_unlock();
1407 if (info) {
1408 /* update lib/pl mode */
1409 if (cur_view < 2)
1410 play_library = 1;
1411 if (cur_view == 2)
1412 play_library = 0;
1414 player_play_file(info);
1418 static void cmd_win_mv_after(char *arg)
1420 editable_lock();
1421 switch (cur_view) {
1422 case TREE_VIEW:
1423 break;
1424 case SORTED_VIEW:
1425 editable_move_after(&lib_editable);
1426 break;
1427 case PLAYLIST_VIEW:
1428 editable_move_after(&pl_editable);
1429 break;
1430 case QUEUE_VIEW:
1431 editable_move_after(&pq_editable);
1432 break;
1433 case BROWSER_VIEW:
1434 break;
1435 case FILTERS_VIEW:
1436 break;
1437 case HELP_VIEW:
1438 break;
1440 editable_unlock();
1443 static void cmd_win_mv_before(char *arg)
1445 editable_lock();
1446 switch (cur_view) {
1447 case TREE_VIEW:
1448 break;
1449 case SORTED_VIEW:
1450 editable_move_before(&lib_editable);
1451 break;
1452 case PLAYLIST_VIEW:
1453 editable_move_before(&pl_editable);
1454 break;
1455 case QUEUE_VIEW:
1456 editable_move_before(&pq_editable);
1457 break;
1458 case BROWSER_VIEW:
1459 break;
1460 case FILTERS_VIEW:
1461 break;
1462 case HELP_VIEW:
1463 break;
1465 editable_unlock();
1468 static void cmd_win_remove(char *arg)
1470 editable_lock();
1471 switch (cur_view) {
1472 case TREE_VIEW:
1473 tree_remove_sel();
1474 break;
1475 case SORTED_VIEW:
1476 editable_remove_sel(&lib_editable);
1477 break;
1478 case PLAYLIST_VIEW:
1479 editable_remove_sel(&pl_editable);
1480 break;
1481 case QUEUE_VIEW:
1482 editable_remove_sel(&pq_editable);
1483 break;
1484 case BROWSER_VIEW:
1485 browser_delete();
1486 break;
1487 case FILTERS_VIEW:
1488 filters_delete_filter();
1489 break;
1490 case HELP_VIEW:
1491 help_remove();
1492 break;
1494 editable_unlock();
1497 static void cmd_win_sel_cur(char *arg)
1499 editable_lock();
1500 switch (cur_view) {
1501 case TREE_VIEW:
1502 tree_sel_current();
1503 break;
1504 case SORTED_VIEW:
1505 sorted_sel_current();
1506 break;
1507 case PLAYLIST_VIEW:
1508 pl_sel_current();
1509 break;
1510 case QUEUE_VIEW:
1511 break;
1512 case BROWSER_VIEW:
1513 break;
1514 case FILTERS_VIEW:
1515 break;
1516 case HELP_VIEW:
1517 break;
1519 editable_unlock();
1522 static void cmd_win_toggle(char *arg)
1524 switch (cur_view) {
1525 case TREE_VIEW:
1526 editable_lock();
1527 tree_toggle_expand_artist();
1528 editable_unlock();
1529 break;
1530 case SORTED_VIEW:
1531 editable_lock();
1532 editable_toggle_mark(&lib_editable);
1533 editable_unlock();
1534 break;
1535 case PLAYLIST_VIEW:
1536 editable_lock();
1537 editable_toggle_mark(&pl_editable);
1538 editable_unlock();
1539 break;
1540 case QUEUE_VIEW:
1541 editable_lock();
1542 editable_toggle_mark(&pq_editable);
1543 editable_unlock();
1544 break;
1545 case BROWSER_VIEW:
1546 break;
1547 case FILTERS_VIEW:
1548 filters_toggle_filter();
1549 break;
1550 case HELP_VIEW:
1551 help_toggle();
1552 break;
1556 static struct window *current_win(void)
1558 switch (cur_view) {
1559 case TREE_VIEW:
1560 return lib_cur_win;
1561 case SORTED_VIEW:
1562 return lib_editable.win;
1563 case PLAYLIST_VIEW:
1564 return pl_editable.win;
1565 case QUEUE_VIEW:
1566 return pq_editable.win;
1567 case BROWSER_VIEW:
1568 return browser_win;
1569 case HELP_VIEW:
1570 return help_win;
1571 case FILTERS_VIEW:
1572 default:
1573 return filters_win;
1577 static void cmd_win_bottom(char *arg)
1579 editable_lock();
1580 window_goto_bottom(current_win());
1581 editable_unlock();
1584 static void cmd_win_down(char *arg)
1586 editable_lock();
1587 window_down(current_win(), 1);
1588 editable_unlock();
1591 static void cmd_win_next(char *arg)
1593 if (cur_view == TREE_VIEW) {
1594 editable_lock();
1595 tree_toggle_active_window();
1596 editable_unlock();
1600 static void cmd_win_pg_down(char *arg)
1602 editable_lock();
1603 window_page_down(current_win());
1604 editable_unlock();
1607 static void cmd_win_pg_up(char *arg)
1609 editable_lock();
1610 window_page_up(current_win());
1611 editable_unlock();
1614 static void cmd_win_top(char *arg)
1616 editable_lock();
1617 window_goto_top(current_win());
1618 editable_unlock();
1621 static void cmd_win_up(char *arg)
1623 editable_lock();
1624 window_up(current_win(), 1);
1625 editable_unlock();
1628 static void cmd_win_update(char *arg)
1630 switch (cur_view) {
1631 case TREE_VIEW:
1632 case SORTED_VIEW:
1633 cmus_update_lib();
1634 break;
1635 case BROWSER_VIEW:
1636 browser_reload();
1637 break;
1641 static void cmd_browser_up(char *arg)
1643 browser_up();
1646 static void cmd_refresh(char *arg)
1648 clearok(curscr, TRUE);
1649 refresh();
1652 static int cmp_intp(const void *ap, const void *bp)
1654 int a = *(int *)ap;
1655 int b = *(int *)bp;
1656 return a - b;
1659 static int *rand_array(int size, int nmax)
1661 int *r = xnew(int, size + 1);
1662 int i, offset = 0;
1663 int count = size;
1665 if (count > nmax / 2) {
1667 * Imagine that there are 1000 tracks in library and we want to
1668 * add 998 random tracks to queue. After we have added 997
1669 * random numbers to the array it would be quite hard to find a
1670 * random number that isn't already in the array (3/1000
1671 * probability).
1673 * So we invert the logic:
1675 * Find two (1000 - 998) random numbers in 0..999 range and put
1676 * them at end of the array. Sort the numbers and then fill
1677 * the array starting at index 0 with incrementing values that
1678 * are not in the set of random numbers.
1680 count = nmax - count;
1681 offset = size - count;
1684 for (i = 0; i < count; ) {
1685 int v, j;
1686 found:
1687 v = rand() % nmax;
1688 for (j = 0; j < i; j++) {
1689 if (r[offset + j] == v)
1690 goto found;
1692 r[offset + i++] = v;
1694 qsort(r + offset, count, sizeof(*r), cmp_intp);
1696 if (offset) {
1697 int j, n;
1699 /* simplifies next loop */
1700 r[size] = nmax;
1702 /* convert the indexes we don't want to those we want */
1703 i = 0;
1704 j = offset;
1705 n = 0;
1706 do {
1707 while (n < r[j])
1708 r[i++] = n++;
1709 j++;
1710 n++;
1711 } while (i < size);
1713 return r;
1716 static int count_albums(void)
1718 struct artist *artist;
1719 struct list_head *item;
1720 int count = 0;
1722 list_for_each_entry(artist, &lib_artist_head, node) {
1723 list_for_each(item, &artist->album_head)
1724 count++;
1726 return count;
1729 struct album_list {
1730 struct list_head node;
1731 const struct album *album;
1734 static void cmd_lqueue(char *arg)
1736 LIST_HEAD(head);
1737 const struct list_head *item;
1738 const struct album *album;
1739 int count = 1, nmax, i, pos;
1740 int *r;
1742 if (arg) {
1743 long int val;
1745 if (str_to_int(arg, &val) || val <= 0) {
1746 error_msg("argument must be positive integer");
1747 return;
1749 count = val;
1751 editable_lock();
1752 nmax = count_albums();
1753 if (count > nmax)
1754 count = nmax;
1755 if (!count)
1756 goto unlock;
1758 r = rand_array(count, nmax);
1759 album = to_album(to_artist(lib_artist_head.next)->album_head.next);
1760 pos = 0;
1761 for (i = 0; i < count; i++) {
1762 struct album_list *a;
1764 while (pos < r[i]) {
1765 struct artist *artist = album->artist;
1766 if (album->node.next == &artist->album_head) {
1767 artist = to_artist(artist->node.next);
1768 album = to_album(artist->album_head.next);
1769 } else {
1770 album = to_album(album->node.next);
1772 pos++;
1774 a = xnew(struct album_list, 1);
1775 a->album = album;
1776 list_add_rand(&head, &a->node, i);
1778 free(r);
1780 item = head.next;
1781 do {
1782 struct list_head *next = item->next;
1783 struct album_list *a = container_of(item, struct album_list, node);
1784 struct tree_track *t;
1786 list_for_each_entry(t, &a->album->track_head, node)
1787 editable_add(&pq_editable, simple_track_new(tree_track_info(t)));
1788 free(a);
1789 item = next;
1790 } while (item != &head);
1791 unlock:
1792 editable_unlock();
1795 static void cmd_tqueue(char *arg)
1797 LIST_HEAD(head);
1798 struct list_head *item;
1799 int count = 1, i, pos;
1800 int *r;
1802 if (arg) {
1803 long int val;
1805 if (str_to_int(arg, &val) || val <= 0) {
1806 error_msg("argument must be positive integer");
1807 return;
1809 count = val;
1811 editable_lock();
1812 if (count > lib_editable.nr_tracks)
1813 count = lib_editable.nr_tracks;
1814 if (!count)
1815 goto unlock;
1817 r = rand_array(count, lib_editable.nr_tracks);
1818 item = lib_editable.head.next;
1819 pos = 0;
1820 for (i = 0; i < count; i++) {
1821 struct simple_track *t;
1823 while (pos < r[i]) {
1824 item = item->next;
1825 pos++;
1827 t = simple_track_new(to_simple_track(item)->info);
1828 list_add_rand(&head, &t->node, i);
1830 free(r);
1832 item = head.next;
1833 do {
1834 struct list_head *next = item->next;
1835 struct simple_track *t = to_simple_track(item);
1836 editable_add(&pq_editable, t);
1837 item = next;
1838 } while (item != &head);
1839 unlock:
1840 editable_unlock();
1843 /* tab exp {{{
1845 * these functions fill tabexp struct, which is resetted beforehand
1848 /* buffer used for tab expansion */
1849 static char expbuf[512];
1851 static int filter_directories(const char *name, const struct stat *s)
1853 return S_ISDIR(s->st_mode);
1856 static int filter_any(const char *name, const struct stat *s)
1858 return 1;
1861 static int filter_playable(const char *name, const struct stat *s)
1863 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1866 static int filter_playlist(const char *name, const struct stat *s)
1868 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1871 static int filter_supported(const char *name, const struct stat *s)
1873 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1876 static void expand_files(const char *str)
1878 expand_files_and_dirs(str, filter_any);
1881 static void expand_directories(const char *str)
1883 expand_files_and_dirs(str, filter_directories);
1886 static void expand_playable(const char *str)
1888 expand_files_and_dirs(str, filter_playable);
1891 static void expand_playlist(const char *str)
1893 expand_files_and_dirs(str, filter_playlist);
1896 static void expand_supported(const char *str)
1898 expand_files_and_dirs(str, filter_supported);
1901 static void expand_add(const char *str)
1903 int flag = parse_flags(&str, "lpqQ");
1905 if (flag == -1)
1906 return;
1907 if (str == NULL)
1908 str = "";
1909 expand_supported(str);
1911 if (tabexp.head && flag) {
1912 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1913 free(tabexp.head);
1914 tabexp.head = xstrdup(expbuf);
1918 static void expand_load_save(const char *str)
1920 int flag = parse_flags(&str, "lp");
1922 if (flag == -1)
1923 return;
1924 if (str == NULL)
1925 str = "";
1926 expand_playlist(str);
1928 if (tabexp.head && flag) {
1929 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1930 free(tabexp.head);
1931 tabexp.head = xstrdup(expbuf);
1935 static void expand_key_context(const char *str, const char *force)
1937 int pos, i, len = strlen(str);
1938 char **tails;
1940 tails = xnew(char *, NR_CTXS + 1);
1941 pos = 0;
1942 for (i = 0; key_context_names[i]; i++) {
1943 int cmp = strncmp(str, key_context_names[i], len);
1944 if (cmp > 0)
1945 continue;
1946 if (cmp < 0)
1947 break;
1948 tails[pos++] = xstrdup(key_context_names[i] + len);
1951 if (pos == 0) {
1952 free(tails);
1953 return;
1955 if (pos == 1) {
1956 char *tmp = xstrjoin(tails[0], " ");
1957 free(tails[0]);
1958 tails[0] = tmp;
1960 tails[pos] = NULL;
1961 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1962 tabexp.head = xstrdup(expbuf);
1963 tabexp.tails = tails;
1966 static int get_context(const char *str, int len)
1968 int i, c = -1, count = 0;
1970 for (i = 0; key_context_names[i]; i++) {
1971 if (strncmp(str, key_context_names[i], len) == 0) {
1972 if (key_context_names[i][len] == 0) {
1973 /* exact */
1974 return i;
1976 c = i;
1977 count++;
1980 if (count == 1)
1981 return c;
1982 return -1;
1985 static void expand_command_line(const char *str);
1987 static void expand_bind_args(const char *str)
1989 /* :bind context key function
1991 * possible values for str:
1993 * context k
1994 * context key f
1996 * you need to know context before you can expand function
1998 /* start and end pointers for context, key and function */
1999 const char *cs, *ce, *ks, *ke, *fs;
2000 int i, c, k, count;
2001 int flag = parse_flags((const char **)&str, "f");
2002 const char *force = "";
2004 if (flag == -1)
2005 return;
2006 if (str == NULL)
2007 str = "";
2009 if (flag == 'f')
2010 force = "-f ";
2012 cs = str;
2013 ce = strchr(cs, ' ');
2014 if (ce == NULL) {
2015 expand_key_context(cs, force);
2016 return;
2019 /* context must be expandable */
2020 c = get_context(cs, ce - cs);
2021 if (c == -1) {
2022 /* context is ambiguous or invalid */
2023 return;
2026 ks = ce;
2027 while (*ks == ' ')
2028 ks++;
2029 ke = strchr(ks, ' ');
2030 if (ke == NULL) {
2031 /* expand key */
2032 int len = strlen(ks);
2033 PTR_ARRAY(array);
2035 for (i = 0; key_table[i].name; i++) {
2036 int cmp = strncmp(ks, key_table[i].name, len);
2037 if (cmp > 0)
2038 continue;
2039 if (cmp < 0)
2040 break;
2041 ptr_array_add(&array, xstrdup(key_table[i].name + len));
2044 if (!array.count)
2045 return;
2047 if (array.count == 1) {
2048 char **ptrs = array.ptrs;
2049 char *tmp = xstrjoin(ptrs[0], " ");
2050 free(ptrs[0]);
2051 ptrs[0] = tmp;
2054 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
2056 ptr_array_plug(&array);
2057 tabexp.head = xstrdup(expbuf);
2058 tabexp.tails = array.ptrs;
2059 return;
2062 /* key must be expandable */
2063 k = -1;
2064 count = 0;
2065 for (i = 0; key_table[i].name; i++) {
2066 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
2067 if (key_table[i].name[ke - ks] == 0) {
2068 /* exact */
2069 k = i;
2070 count = 1;
2071 break;
2073 k = i;
2074 count++;
2077 if (count != 1) {
2078 /* key is ambiguous or invalid */
2079 return;
2082 fs = ke;
2083 while (*fs == ' ')
2084 fs++;
2086 if (*fs == ':')
2087 fs++;
2089 /* expand com [arg...] */
2090 expand_command_line(fs);
2091 if (tabexp.head == NULL) {
2092 /* command expand failed */
2093 return;
2097 * tabexp.head is now "com"
2098 * tabexp.tails is [ mand1 mand2 ... ]
2100 * need to change tabexp.head to "context key com"
2103 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
2104 key_table[k].name, tabexp.head);
2105 free(tabexp.head);
2106 tabexp.head = xstrdup(expbuf);
2109 static void expand_unbind_args(const char *str)
2111 /* :unbind context key */
2112 /* start and end pointers for context and key */
2113 const char *cs, *ce, *ks;
2114 const struct binding *b;
2115 PTR_ARRAY(array);
2116 int c, len;
2118 cs = str;
2119 ce = strchr(cs, ' ');
2120 if (ce == NULL) {
2121 expand_key_context(cs, "");
2122 return;
2125 /* context must be expandable */
2126 c = get_context(cs, ce - cs);
2127 if (c == -1) {
2128 /* context is ambiguous or invalid */
2129 return;
2132 ks = ce;
2133 while (*ks == ' ')
2134 ks++;
2136 /* expand key */
2137 len = strlen(ks);
2138 b = key_bindings[c];
2139 while (b) {
2140 if (!strncmp(ks, b->key->name, len))
2141 ptr_array_add(&array, xstrdup(b->key->name + len));
2142 b = b->next;
2144 if (!array.count)
2145 return;
2147 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
2149 ptr_array_plug(&array);
2150 tabexp.head = xstrdup(expbuf);
2151 tabexp.tails = array.ptrs;
2154 static void expand_factivate(const char *str)
2156 /* "name1 name2 name3", expand only name3 */
2157 struct filter_entry *e;
2158 const char *name;
2159 PTR_ARRAY(array);
2160 int str_len, len, i;
2162 str_len = strlen(str);
2163 i = str_len;
2164 while (i > 0) {
2165 if (str[i - 1] == ' ')
2166 break;
2167 i--;
2169 len = str_len - i;
2170 name = str + i;
2172 list_for_each_entry(e, &filters_head, node) {
2173 if (!strncmp(name, e->name, len))
2174 ptr_array_add(&array, xstrdup(e->name + len));
2176 if (!array.count)
2177 return;
2179 ptr_array_plug(&array);
2180 tabexp.head = xstrdup(str);
2181 tabexp.tails = array.ptrs;
2184 static void expand_options(const char *str)
2186 struct cmus_opt *opt;
2187 int len;
2188 char **tails;
2190 /* tabexp is resetted */
2191 len = strlen(str);
2192 if (len > 1 && str[len - 1] == '=') {
2193 /* expand value */
2194 char *var = xstrndup(str, len - 1);
2196 list_for_each_entry(opt, &option_head, node) {
2197 if (strcmp(var, opt->name) == 0) {
2198 char buf[OPTION_MAX_SIZE];
2200 tails = xnew(char *, 2);
2202 buf[0] = 0;
2203 opt->get(opt->id, buf);
2204 tails[0] = xstrdup(buf);
2205 tails[1] = NULL;
2207 tabexp.head = xstrdup(str);
2208 tabexp.tails = tails;
2209 free(var);
2210 return;
2213 free(var);
2214 } else {
2215 /* expand variable */
2216 int pos;
2218 tails = xnew(char *, nr_options + 1);
2219 pos = 0;
2220 list_for_each_entry(opt, &option_head, node) {
2221 if (strncmp(str, opt->name, len) == 0)
2222 tails[pos++] = xstrdup(opt->name + len);
2224 if (pos > 0) {
2225 if (pos == 1) {
2226 /* only one variable matches, add '=' */
2227 char *tmp = xstrjoin(tails[0], "=");
2229 free(tails[0]);
2230 tails[0] = tmp;
2233 tails[pos] = NULL;
2234 tabexp.head = xstrdup(str);
2235 tabexp.tails = tails;
2236 } else {
2237 free(tails);
2242 static void expand_toptions(const char *str)
2244 struct cmus_opt *opt;
2245 int len, pos;
2246 char **tails;
2248 tails = xnew(char *, nr_options + 1);
2249 len = strlen(str);
2250 pos = 0;
2251 list_for_each_entry(opt, &option_head, node) {
2252 if (opt->toggle == NULL)
2253 continue;
2254 if (strncmp(str, opt->name, len) == 0)
2255 tails[pos++] = xstrdup(opt->name + len);
2257 if (pos > 0) {
2258 tails[pos] = NULL;
2259 tabexp.head = xstrdup(str);
2260 tabexp.tails = tails;
2261 } else {
2262 free(tails);
2266 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2268 struct directory dir;
2269 const char *name, *dot;
2270 int len = strlen(str);
2272 if (dir_open(&dir, dirname))
2273 return;
2275 while ((name = dir_read(&dir))) {
2276 if (!S_ISREG(dir.st.st_mode))
2277 continue;
2278 if (strncmp(name, str, len))
2279 continue;
2280 dot = strrchr(name, '.');
2281 if (dot == NULL || strcmp(dot, ".theme"))
2282 continue;
2283 if (dot - name < len)
2284 /* str is "foo.th"
2285 * matches "foo.theme"
2286 * which also ends with ".theme"
2288 continue;
2289 ptr_array_add(array, xstrndup(name + len, dot - name - len));
2291 dir_close(&dir);
2294 static void expand_colorscheme(const char *str)
2296 PTR_ARRAY(array);
2298 load_themes(cmus_config_dir, str, &array);
2299 load_themes(DATADIR "/cmus", str, &array);
2301 if (array.count) {
2302 ptr_array_sort(&array, strptrcmp);
2304 ptr_array_plug(&array);
2305 tabexp.head = xstrdup(str);
2306 tabexp.tails = array.ptrs;
2310 /* tab exp }}} */
2312 /* sort by name */
2313 struct command commands[] = {
2314 { "add", cmd_add, 1, 1, expand_add, 0, 0 },
2315 { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
2316 { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
2317 { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
2318 { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
2319 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
2320 { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
2321 { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
2322 { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
2323 { "fset", cmd_fset, 1, 1, NULL, 0, 0 },
2324 { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
2325 { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
2326 { "lqueue", cmd_lqueue, 0, 1, NULL, 0, 0 },
2327 { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
2328 { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
2329 { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
2330 { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
2331 { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
2332 { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
2333 { "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
2334 { "quit", cmd_quit, 0, 0, NULL, 0, 0 },
2335 { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
2336 { "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
2337 { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
2338 { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
2339 { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
2340 { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
2341 { "set", cmd_set, 1, 1, expand_options, 0, 0 },
2342 { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
2343 { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
2344 { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
2345 { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
2346 { "tqueue", cmd_tqueue, 0, 1, NULL, 0, 0 },
2347 { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
2348 { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
2349 { "update-cache", cmd_update_cache,0, 0, NULL, 0, 0 },
2350 { "view", cmd_view, 1, 1, NULL, 0, 0 },
2351 { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
2352 { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
2353 { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
2354 { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
2355 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
2356 { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
2357 { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
2358 { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
2359 { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
2360 { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
2361 { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
2362 { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
2363 { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
2364 { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
2365 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
2366 { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
2367 { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
2368 { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
2369 { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
2370 { NULL, NULL, 0, 0, 0, 0, 0 }
2373 /* fills tabexp struct */
2374 static void expand_commands(const char *str)
2376 int i, len, pos;
2377 char **tails;
2379 /* tabexp is resetted */
2380 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2381 len = strlen(str);
2382 pos = 0;
2383 for (i = 0; commands[i].name; i++) {
2384 if (strncmp(str, commands[i].name, len) == 0)
2385 tails[pos++] = xstrdup(commands[i].name + len);
2387 if (pos > 0) {
2388 if (pos == 1) {
2389 /* only one command matches, add ' ' */
2390 char *tmp = xstrjoin(tails[0], " ");
2392 free(tails[0]);
2393 tails[0] = tmp;
2395 tails[pos] = NULL;
2396 tabexp.head = xstrdup(str);
2397 tabexp.tails = tails;
2398 } else {
2399 free(tails);
2403 struct command *get_command(const char *str)
2405 int i, len;
2407 while (*str == ' ')
2408 str++;
2409 for (len = 0; str[len] && str[len] != ' '; len++)
2412 for (i = 0; commands[i].name; i++) {
2413 if (strncmp(str, commands[i].name, len))
2414 continue;
2416 if (commands[i].name[len] == 0) {
2417 /* exact */
2418 return &commands[i];
2421 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2422 /* ambiguous */
2423 return NULL;
2425 return &commands[i];
2427 return NULL;
2430 /* fills tabexp struct */
2431 static void expand_command_line(const char *str)
2433 /* :command [arg]...
2435 * examples:
2437 * str expanded value (tabexp.head)
2438 * -------------------------------------
2439 * fs fset
2440 * b c bind common
2441 * se se (tabexp.tails = [ ek t ])
2443 /* command start/end, argument start */
2444 const char *cs, *ce, *as;
2445 const struct command *cmd;
2447 cs = str;
2448 ce = strchr(cs, ' ');
2449 if (ce == NULL) {
2450 /* expand command */
2451 expand_commands(cs);
2452 return;
2455 /* command must be expandable */
2456 cmd = get_command(cs);
2457 if (cmd == NULL) {
2458 /* command ambiguous or invalid */
2459 return;
2462 if (cmd->expand == NULL) {
2463 /* can't expand argument */
2464 return;
2467 as = ce;
2468 while (*as == ' ')
2469 as++;
2471 /* expand argument */
2472 cmd->expand(as);
2473 if (tabexp.head == NULL) {
2474 /* argument expansion failed */
2475 return;
2478 /* tabexp.head is now start of the argument string */
2479 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2480 free(tabexp.head);
2481 tabexp.head = xstrdup(expbuf);
2484 static void tab_expand(void)
2486 char *s1, *s2, *tmp;
2487 int pos;
2489 /* strip white space */
2490 pos = 0;
2491 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2492 pos++;
2494 /* string to expand */
2495 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2497 /* tail */
2498 s2 = xstrdup(cmdline.line + cmdline.bpos);
2500 tmp = tabexp_expand(s1, expand_command_line);
2501 if (tmp) {
2502 /* tmp.s2 */
2503 int l1, l2;
2505 l1 = strlen(tmp);
2506 l2 = strlen(s2);
2507 cmdline.blen = l1 + l2;
2508 if (cmdline.blen >= cmdline.size) {
2509 while (cmdline.blen >= cmdline.size)
2510 cmdline.size *= 2;
2511 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2513 sprintf(cmdline.line, "%s%s", tmp, s2);
2514 cmdline.bpos = l1;
2515 cmdline.cpos = u_strlen(tmp);
2516 cmdline.clen = u_strlen(cmdline.line);
2517 free(tmp);
2519 free(s1);
2520 free(s2);
2523 static void reset_tab_expansion(void)
2525 tabexp_reset();
2526 arg_expand_cmd = -1;
2529 int parse_command(const char *buf, char **cmdp, char **argp)
2531 int cmd_start, cmd_end, cmd_len;
2532 int arg_start, arg_end;
2533 int i;
2535 i = 0;
2536 while (buf[i] && buf[i] == ' ')
2537 i++;
2539 if (buf[i] == '#')
2540 return 0;
2542 cmd_start = i;
2543 while (buf[i] && buf[i] != ' ')
2544 i++;
2545 cmd_end = i;
2546 while (buf[i] && buf[i] == ' ')
2547 i++;
2548 arg_start = i;
2549 while (buf[i])
2550 i++;
2551 arg_end = i;
2553 cmd_len = cmd_end - cmd_start;
2554 if (cmd_len == 0)
2555 return 0;
2557 *cmdp = xstrndup(buf + cmd_start, cmd_len);
2558 if (arg_start == arg_end) {
2559 *argp = NULL;
2560 } else {
2561 *argp = xstrndup(buf + arg_start, arg_end - arg_start);
2563 return 1;
2566 int run_only_safe_commands;
2568 void run_parsed_command(char *cmd, char *arg)
2570 int cmd_len = strlen(cmd);
2571 int i = 0;
2573 while (1) {
2574 const struct command *c = &commands[i];
2576 if (c->name == NULL) {
2577 error_msg("unknown command\n");
2578 break;
2580 if (strncmp(cmd, c->name, cmd_len) == 0) {
2581 const char *next = commands[i + 1].name;
2582 int exact = c->name[cmd_len] == 0;
2584 if (!exact && next && strncmp(cmd, next, cmd_len) == 0) {
2585 error_msg("ambiguous command\n");
2586 break;
2588 if (c->min_args > 0 && arg == NULL) {
2589 error_msg("not enough arguments\n");
2590 break;
2592 if (c->max_args == 0 && arg) {
2593 error_msg("too many arguments\n");
2594 break;
2596 if (run_only_safe_commands && c->flags & CMD_UNSAFE) {
2597 d_print("trying to execute unsafe command over net\n");
2598 break;
2600 c->func(arg);
2601 break;
2603 i++;
2607 void run_command(const char *buf)
2609 char *cmd, *arg;
2611 if (!parse_command(buf, &cmd, &arg))
2612 return;
2614 run_parsed_command(cmd, arg);
2615 free(arg);
2616 free(cmd);
2619 static void reset_history_search(void)
2621 history_reset_search(&cmd_history);
2622 free(history_search_text);
2623 history_search_text = NULL;
2626 static void backspace(void)
2628 if (cmdline.clen > 0) {
2629 cmdline_backspace();
2630 } else {
2631 input_mode = NORMAL_MODE;
2635 void command_mode_ch(uchar ch)
2637 switch (ch) {
2638 case 0x01: // ^A
2639 cmdline_move_home();
2640 break;
2641 case 0x02: // ^B
2642 cmdline_move_left();
2643 break;
2644 case 0x04: // ^D
2645 cmdline_delete_ch();
2646 break;
2647 case 0x05: // ^E
2648 cmdline_move_end();
2649 break;
2650 case 0x06: // ^F
2651 cmdline_move_right();
2652 break;
2653 case 0x03: // ^C
2654 case 0x07: // ^G
2655 case 0x1B: // ESC
2656 if (cmdline.blen) {
2657 history_add_line(&cmd_history, cmdline.line);
2658 cmdline_clear();
2660 input_mode = NORMAL_MODE;
2661 break;
2662 case 0x0A:
2663 if (cmdline.blen) {
2664 run_command(cmdline.line);
2665 history_add_line(&cmd_history, cmdline.line);
2666 cmdline_clear();
2668 input_mode = NORMAL_MODE;
2669 break;
2670 case 0x0B:
2671 cmdline_clear_end();
2672 break;
2673 case 0x09:
2674 tab_expand();
2675 break;
2676 case 0x15:
2677 cmdline_backspace_to_bol();
2678 break;
2679 case 0x08: // ^H
2680 case 127:
2681 backspace();
2682 break;
2683 default:
2684 cmdline_insert_ch(ch);
2686 reset_history_search();
2687 if (ch != 0x09)
2688 reset_tab_expansion();
2691 void command_mode_key(int key)
2693 reset_tab_expansion();
2694 switch (key) {
2695 case KEY_DC:
2696 cmdline_delete_ch();
2697 break;
2698 case KEY_BACKSPACE:
2699 backspace();
2700 break;
2701 case KEY_LEFT:
2702 cmdline_move_left();
2703 return;
2704 case KEY_RIGHT:
2705 cmdline_move_right();
2706 return;
2707 case KEY_HOME:
2708 cmdline_move_home();
2709 return;
2710 case KEY_END:
2711 cmdline_move_end();
2712 return;
2713 case KEY_UP:
2715 const char *s;
2717 if (history_search_text == NULL)
2718 history_search_text = xstrdup(cmdline.line);
2719 s = history_search_forward(&cmd_history, history_search_text);
2720 if (s)
2721 cmdline_set_text(s);
2723 return;
2724 case KEY_DOWN:
2725 if (history_search_text) {
2726 const char *s;
2728 s = history_search_backward(&cmd_history, history_search_text);
2729 if (s) {
2730 cmdline_set_text(s);
2731 } else {
2732 cmdline_set_text(history_search_text);
2735 return;
2736 default:
2737 d_print("key = %c (%d)\n", key, key);
2739 reset_history_search();
2742 void commands_init(void)
2744 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2745 history_load(&cmd_history, cmd_history_filename, 2000);
2748 void commands_exit(void)
2750 history_save(&cmd_history);
2751 free(cmd_history_filename);
2752 tabexp_reset();