2.2.0
[cmus.git] / command_mode.c
blobe30d65bf14f447bd694a17d36bf1590339590523
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 "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 "format_print.h"
43 #include "spawn.h"
44 #include "utils.h"
45 #include "list.h"
46 #include "debug.h"
47 #include "load_dir.h"
48 #include "config/datadir.h"
49 #include "help.h"
51 #include <stdlib.h>
52 #include <ctype.h>
53 #include <sys/types.h>
54 #include <sys/wait.h>
55 #include <dirent.h>
56 #include <pwd.h>
58 #if defined(__sun__)
59 #include <ncurses.h>
60 #else
61 #include <curses.h>
62 #endif
64 static struct history cmd_history;
65 static char *cmd_history_filename;
66 static char *history_search_text = NULL;
67 static int arg_expand_cmd = -1;
68 static int prev_view = -1;
70 static char *get_home_dir(const char *username)
72 struct passwd *passwd;
74 if (username == NULL)
75 return xstrdup(home_dir);
76 passwd = getpwnam(username);
77 if (passwd == NULL)
78 return NULL;
79 /* don't free passwd */
80 return xstrdup(passwd->pw_dir);
83 static char *expand_filename(const char *name)
85 if (name[0] == '~') {
86 char *slash;
88 slash = strchr(name, '/');
89 if (slash) {
90 char *username, *home;
92 if (slash - name - 1 > 0) {
93 /* ~user/... */
94 username = xstrndup(name + 1, slash - name - 1);
95 } else {
96 /* ~/... */
97 username = NULL;
99 home = get_home_dir(username);
100 free(username);
101 if (home) {
102 char *expanded;
104 expanded = xstrjoin(home, slash);
105 free(home);
106 return expanded;
107 } else {
108 return xstrdup(name);
110 } else {
111 if (name[1] == 0) {
112 return xstrdup(home_dir);
113 } else {
114 char *home;
116 home = get_home_dir(name + 1);
117 if (home)
118 return home;
119 return xstrdup(name);
122 } else {
123 return xstrdup(name);
127 /* view {{{ */
129 void view_clear(int view)
131 switch (view) {
132 case TREE_VIEW:
133 case SORTED_VIEW:
134 worker_remove_jobs(JOB_TYPE_LIB);
135 editable_lock();
136 editable_clear(&lib_editable);
138 /* FIXME: make this optional? */
139 lib_clear_store();
141 editable_unlock();
142 break;
143 case PLAYLIST_VIEW:
144 worker_remove_jobs(JOB_TYPE_PL);
145 editable_lock();
146 editable_clear(&pl_editable);
147 editable_unlock();
148 break;
149 case QUEUE_VIEW:
150 worker_remove_jobs(JOB_TYPE_QUEUE);
151 editable_lock();
152 editable_clear(&pq_editable);
153 editable_unlock();
154 break;
155 default:
156 info_msg(":clear only works in views 1-4");
160 void view_add(int view, char *arg, int prepend)
162 char *tmp, *name;
163 enum file_type ft;
165 tmp = expand_filename(arg);
166 ft = cmus_detect_ft(tmp, &name);
167 if (ft == FILE_TYPE_INVALID) {
168 error_msg("adding '%s': %s", tmp, strerror(errno));
169 free(tmp);
170 return;
172 free(tmp);
174 switch (view) {
175 case TREE_VIEW:
176 case SORTED_VIEW:
177 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
178 break;
179 case PLAYLIST_VIEW:
180 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
181 break;
182 case QUEUE_VIEW:
183 if (prepend) {
184 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
185 } else {
186 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
188 break;
189 default:
190 info_msg(":add only works in views 1-4");
192 free(name);
195 void view_load(int view, char *arg)
197 char *tmp, *name;
198 enum file_type ft;
200 tmp = expand_filename(arg);
201 ft = cmus_detect_ft(tmp, &name);
202 if (ft == FILE_TYPE_INVALID) {
203 error_msg("loading '%s': %s", tmp, strerror(errno));
204 free(tmp);
205 return;
207 free(tmp);
209 if (ft == FILE_TYPE_FILE)
210 ft = FILE_TYPE_PL;
211 if (ft != FILE_TYPE_PL) {
212 error_msg("loading '%s': not a playlist file", name);
213 free(name);
214 return;
217 switch (view) {
218 case TREE_VIEW:
219 case SORTED_VIEW:
220 worker_remove_jobs(JOB_TYPE_LIB);
221 editable_lock();
222 editable_clear(&lib_editable);
223 editable_unlock();
224 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
225 free(lib_filename);
226 lib_filename = name;
227 break;
228 case PLAYLIST_VIEW:
229 worker_remove_jobs(JOB_TYPE_PL);
230 editable_lock();
231 editable_clear(&pl_editable);
232 editable_unlock();
233 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
234 free(pl_filename);
235 pl_filename = name;
236 break;
237 default:
238 info_msg(":load only works in views 1-3");
239 free(name);
243 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
245 char *filename = *filenamep;
247 if (arg) {
248 free(filename);
249 filename = xstrdup(arg);
250 *filenamep = filename;
253 editable_lock();
254 if (cmus_save(for_each_ti, filename) == -1)
255 error_msg("saving '%s': %s", filename, strerror(errno));
256 editable_unlock();
259 void view_save(int view, char *arg)
261 if (arg) {
262 char *tmp;
264 tmp = expand_filename(arg);
265 arg = path_absolute(tmp);
266 free(tmp);
269 switch (view) {
270 case TREE_VIEW:
271 case SORTED_VIEW:
272 if (worker_has_job(JOB_TYPE_LIB))
273 goto worker_running;
274 do_save(lib_for_each, arg, &lib_filename);
275 break;
276 case PLAYLIST_VIEW:
277 if (worker_has_job(JOB_TYPE_PL))
278 goto worker_running;
279 do_save(pl_for_each, arg, &pl_filename);
280 break;
281 default:
282 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
284 free(arg);
285 return;
286 worker_running:
287 error_msg("can't save when tracks are being added");
288 free(arg);
291 /* }}} */
293 /* only returns the last flag which is enough for it's callers */
294 static int parse_flags(const char **strp, const char *flags)
296 const char *str = *strp;
297 int flag = 0;
299 if (str == NULL)
300 return flag;
302 while (*str) {
303 if (*str != '-')
304 break;
306 // "-"
307 if (str[1] == 0)
308 break;
310 // "--" or "-- "
311 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
312 str += 2;
313 break;
316 // not "-?" or "-? "
317 if (str[2] && str[2] != ' ')
318 break;
320 flag = str[1];
321 if (!strchr(flags, flag)) {
322 error_msg("invalid option -%c", flag);
323 return -1;
326 str += 2;
328 while (*str == ' ')
329 str++;
331 while (*str == ' ')
332 str++;
333 if (*str == 0)
334 str = NULL;
335 *strp = str;
336 return flag;
339 static int flag_to_view(int flag)
341 switch (flag) {
342 case 'l':
343 return TREE_VIEW;
344 case 'p':
345 return PLAYLIST_VIEW;
346 case 'q':
347 case 'Q':
348 return QUEUE_VIEW;
349 default:
350 return cur_view;
354 static void cmd_add(char *arg)
356 int flag = parse_flags((const char **)&arg, "lpqQ");
358 if (flag == -1)
359 return;
360 if (arg == NULL) {
361 error_msg("not enough arguments\n");
362 return;
364 view_add(flag_to_view(flag), arg, flag == 'Q');
367 static void cmd_clear(char *arg)
369 int flag = parse_flags((const char **)&arg, "lpq");
371 if (flag == -1)
372 return;
373 if (arg) {
374 error_msg("too many arguments\n");
375 return;
377 view_clear(flag_to_view(flag));
380 static void cmd_load(char *arg)
382 int flag = parse_flags((const char **)&arg, "lp");
384 if (flag == -1)
385 return;
386 if (arg == NULL) {
387 error_msg("not enough arguments\n");
388 return;
390 view_load(flag_to_view(flag), arg);
393 static void cmd_save(char *arg)
395 int flag = parse_flags((const char **)&arg, "lp");
397 if (flag == -1)
398 return;
399 view_save(flag_to_view(flag), arg);
402 static void cmd_set(char *arg)
404 char *value = NULL;
405 int i;
407 for (i = 0; arg[i]; i++) {
408 if (arg[i] == '=') {
409 arg[i] = 0;
410 value = &arg[i + 1];
411 break;
414 if (value) {
415 option_set(arg, value);
416 help_win->changed = 1;
417 } else {
418 struct cmus_opt *opt;
419 char buf[OPTION_MAX_SIZE];
421 /* support "set <option>?" */
422 i--;
423 if (arg[i] == '?')
424 arg[i] = 0;
426 opt = option_find(arg);
427 if (opt) {
428 opt->get(opt->id, buf);
429 info_msg("setting: '%s=%s'", arg, buf);
434 static void cmd_toggle(char *arg)
436 struct cmus_opt *opt = option_find(arg);
438 if (opt == NULL)
439 return;
441 if (opt->toggle == NULL) {
442 error_msg("%s is not toggle option", opt->name);
443 return;
445 opt->toggle(opt->id);
446 help_win->changed = 1;
449 static int get_number(char *str, char **end)
451 int val = 0;
453 while (*str >= '0' && *str <= '9') {
454 val *= 10;
455 val += *str++ - '0';
457 *end = str;
458 return val;
461 static void cmd_seek(char *arg)
463 int relative = 0;
464 int seek = 0, sign = 1, count;
466 switch (*arg) {
467 case '-':
468 sign = -1;
469 case '+':
470 relative = 1;
471 arg++;
472 break;
475 count = 0;
476 goto inside;
478 do {
479 int num;
480 char *end;
482 if (*arg != ':')
483 break;
484 arg++;
485 inside:
486 num = get_number(arg, &end);
487 if (arg == end)
488 break;
489 arg = end;
490 seek = seek * 60 + num;
491 } while (++count < 3);
493 seek *= sign;
494 if (!count)
495 goto err;
497 if (count == 1) {
498 switch (tolower(*arg)) {
499 case 'h':
500 seek *= 60;
501 case 'm':
502 seek *= 60;
503 case 's':
504 arg++;
505 break;
509 if (!*arg) {
510 player_seek(seek, relative);
511 return;
513 err:
514 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
517 static void cmd_factivate(char *arg)
519 editable_lock();
520 filters_activate_names(arg);
521 editable_unlock();
524 static void cmd_filter(char *arg)
526 editable_lock();
527 filters_set_anonymous(arg);
528 editable_unlock();
531 static void cmd_fset(char *arg)
533 filters_set_filter(arg);
536 static void cmd_invert(char *arg)
538 editable_lock();
539 switch (cur_view) {
540 case SORTED_VIEW:
541 editable_invert_marks(&lib_editable);
542 break;
543 case PLAYLIST_VIEW:
544 editable_invert_marks(&pl_editable);
545 break;
546 case QUEUE_VIEW:
547 editable_invert_marks(&pq_editable);
548 break;
549 default:
550 info_msg(":invert only works in views 2-4");
552 editable_unlock();
555 static void cmd_mark(char *arg)
557 editable_lock();
558 switch (cur_view) {
559 case SORTED_VIEW:
560 editable_mark(&lib_editable, arg);
561 break;
562 case PLAYLIST_VIEW:
563 editable_mark(&pl_editable, arg);
564 break;
565 case QUEUE_VIEW:
566 editable_mark(&pq_editable, arg);
567 break;
568 default:
569 info_msg(":mark only works in views 2-4");
571 editable_unlock();
574 static void cmd_unmark(char *arg)
576 editable_lock();
577 switch (cur_view) {
578 case SORTED_VIEW:
579 editable_unmark(&lib_editable);
580 break;
581 case PLAYLIST_VIEW:
582 editable_unmark(&pl_editable);
583 break;
584 case QUEUE_VIEW:
585 editable_unmark(&pq_editable);
586 break;
587 default:
588 info_msg(":unmark only works in views 2-4");
590 editable_unlock();
593 static void cmd_cd(char *arg)
595 if (arg) {
596 char *dir, *absolute;
598 dir = expand_filename(arg);
599 absolute = path_absolute(dir);
600 if (chdir(dir) == -1) {
601 error_msg("could not cd to '%s': %s", dir, strerror(errno));
602 } else {
603 browser_chdir(absolute);
605 free(absolute);
606 free(dir);
607 } else {
608 if (chdir(home_dir) == -1) {
609 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
610 } else {
611 browser_chdir(home_dir);
616 static void cmd_bind(char *arg)
618 int flag = parse_flags((const char **)&arg, "f");
619 char *key, *func;
621 if (flag == -1)
622 return;
624 if (arg == NULL)
625 goto err;
627 key = strchr(arg, ' ');
628 if (key == NULL)
629 goto err;
630 *key++ = 0;
631 while (*key == ' ')
632 key++;
634 func = strchr(key, ' ');
635 if (func == NULL)
636 goto err;
637 *func++ = 0;
638 while (*func == ' ')
639 func++;
640 if (*func == 0)
641 goto err;
643 key_bind(arg, key, func, flag == 'f');
644 return;
645 err:
646 error_msg("expecting 3 arguments (context, key and function)\n");
649 static void cmd_unbind(char *arg)
651 int flag = parse_flags((const char **)&arg, "f");
652 char *key;
654 if (flag == -1)
655 return;
657 if (arg == NULL)
658 goto err;
660 key = strchr(arg, ' ');
661 if (key == NULL)
662 goto err;
663 *key++ = 0;
664 while (*key == ' ')
665 key++;
666 if (*key == 0)
667 goto err;
669 /* FIXME: remove spaces at end */
671 key_unbind(arg, key, flag == 'f');
672 return;
673 err:
674 error_msg("expecting 2 arguments (context and key)\n");
677 static void cmd_showbind(char *arg)
679 char *key;
681 key = strchr(arg, ' ');
682 if (key == NULL)
683 goto err;
684 *key++ = 0;
685 while (*key == ' ')
686 key++;
687 if (*key == 0)
688 goto err;
690 /* FIXME: remove spaces at end */
692 show_binding(arg, key);
693 return;
694 err:
695 error_msg("expecting 2 arguments (context and key)\n");
698 static void cmd_quit(char *arg)
700 if (!worker_has_job(JOB_TYPE_ANY) || yes_no_query("Tracks are being added. Quit and truncate playlist(s)? [y/N]"))
701 cmus_running = 0;
704 static void cmd_reshuffle(char *arg)
706 editable_lock();
707 lib_reshuffle();
708 pl_reshuffle();
709 editable_unlock();
712 static void cmd_source(char *arg)
714 char *filename = expand_filename(arg);
716 if (source_file(filename) == -1)
717 error_msg("sourcing %s: %s", filename, strerror(errno));
718 free(filename);
721 static void cmd_colorscheme(char *arg)
723 char filename[512];
725 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
726 if (source_file(filename) == -1) {
727 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
728 if (source_file(filename) == -1)
729 error_msg("sourcing %s: %s", filename, strerror(errno));
734 * \" inside double-quotes becomes "
735 * \\ inside double-quotes becomes \
737 static char *parse_quoted(const char **strp)
739 const char *str = *strp;
740 const char *start;
741 char *ret, *dst;
743 str++;
744 start = str;
745 while (1) {
746 int c = *str++;
748 if (c == 0)
749 goto error;
750 if (c == '"')
751 break;
752 if (c == '\\') {
753 if (*str++ == 0)
754 goto error;
757 *strp = str;
758 ret = xnew(char, str - start);
759 str = start;
760 dst = ret;
761 while (1) {
762 int c = *str++;
764 if (c == '"')
765 break;
766 if (c == '\\') {
767 c = *str++;
768 if (c != '"' && c != '\\')
769 *dst++ = '\\';
771 *dst++ = c;
773 *dst = 0;
774 return ret;
775 error:
776 error_msg("`\"' expected");
777 return NULL;
780 static char *parse_escaped(const char **strp)
782 const char *str = *strp;
783 const char *start;
784 char *ret, *dst;
786 start = str;
787 while (1) {
788 int c = *str;
790 if (c == 0 || c == ' ' || c == '\'' || c == '"')
791 break;
793 str++;
794 if (c == '\\') {
795 c = *str;
796 if (c == 0)
797 break;
798 str++;
801 *strp = str;
802 ret = xnew(char, str - start + 1);
803 str = start;
804 dst = ret;
805 while (1) {
806 int c = *str;
808 if (c == 0 || c == ' ' || c == '\'' || c == '"')
809 break;
811 str++;
812 if (c == '\\') {
813 c = *str;
814 if (c == 0) {
815 *dst++ = '\\';
816 break;
818 str++;
820 *dst++ = c;
822 *dst = 0;
823 return ret;
826 static char *parse_one(const char **strp)
828 const char *str = *strp;
829 char *ret = NULL;
831 while (1) {
832 char *part;
833 int c = *str;
835 if (!c || c == ' ')
836 break;
837 if (c == '"') {
838 part = parse_quoted(&str);
839 if (part == NULL)
840 goto error;
841 } else if (c == '\'') {
842 /* backslashes are normal chars inside single-quotes */
843 const char *end;
845 str++;
846 end = strchr(str, '\'');
847 if (end == NULL)
848 goto sq_missing;
849 part = xstrndup(str, end - str);
850 str = end + 1;
851 } else {
852 part = parse_escaped(&str);
855 if (ret == NULL) {
856 ret = part;
857 } else {
858 char *tmp = xstrjoin(ret, part);
859 free(ret);
860 ret = tmp;
863 *strp = str;
864 return ret;
865 sq_missing:
866 error_msg("`'' expected");
867 error:
868 free(ret);
869 return NULL;
872 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
874 char **av = NULL;
875 int nr = 0;
876 int alloc = 0;
878 while (*cmd) {
879 char *arg;
881 /* there can't be spaces at start of command
882 * and there is at least one argument */
883 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
884 /* {} is replaced with file arguments */
885 if (*args_idx != -1)
886 goto only_once_please;
887 *args_idx = nr;
888 cmd += 2;
889 goto skip_spaces;
890 } else {
891 arg = parse_one(&cmd);
892 if (arg == NULL)
893 goto error;
896 if (nr == alloc) {
897 alloc = alloc ? alloc * 2 : 4;
898 av = xrenew(char *, av, alloc + 1);
900 av[nr++] = arg;
901 skip_spaces:
902 while (*cmd == ' ')
903 cmd++;
905 av[nr] = NULL;
906 *ac = nr;
907 return av;
908 only_once_please:
909 error_msg("{} can be used only once");
910 error:
911 while (nr > 0)
912 free(av[--nr]);
913 free(av);
914 return NULL;
917 static struct track_info **sel_tis;
918 static int sel_tis_alloc;
919 static int sel_tis_nr;
921 static int add_ti(void *data, struct track_info *ti)
923 if (sel_tis_nr == sel_tis_alloc) {
924 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
925 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
927 track_info_ref(ti);
928 sel_tis[sel_tis_nr++] = ti;
929 return 0;
932 static void cmd_run(char *arg)
934 char **av, **argv;
935 int ac, argc, i, run, files_idx = -1;
937 if (cur_view > QUEUE_VIEW) {
938 info_msg("Command execution is supported only in views 1-4");
939 return;
942 av = parse_cmd(arg, &files_idx, &ac);
943 if (av == NULL) {
944 return;
947 /* collect selected files (struct track_info) */
948 sel_tis = NULL;
949 sel_tis_alloc = 0;
950 sel_tis_nr = 0;
952 editable_lock();
953 switch (cur_view) {
954 case TREE_VIEW:
955 __tree_for_each_sel(add_ti, NULL, 0);
956 break;
957 case SORTED_VIEW:
958 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
959 break;
960 case PLAYLIST_VIEW:
961 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
962 break;
963 case QUEUE_VIEW:
964 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
965 break;
967 editable_unlock();
969 if (sel_tis_nr == 0) {
970 /* no files selected, do nothing */
971 free_str_array(av);
972 return;
974 sel_tis[sel_tis_nr] = NULL;
976 /* build argv */
977 argv = xnew(char *, ac + sel_tis_nr + 1);
978 argc = 0;
979 if (files_idx == -1) {
980 /* add selected files after rest of the args */
981 for (i = 0; i < ac; i++)
982 argv[argc++] = av[i];
983 for (i = 0; i < sel_tis_nr; i++)
984 argv[argc++] = sel_tis[i]->filename;
985 } else {
986 for (i = 0; i < files_idx; i++)
987 argv[argc++] = av[i];
988 for (i = 0; i < sel_tis_nr; i++)
989 argv[argc++] = sel_tis[i]->filename;
990 for (i = files_idx; i < ac; i++)
991 argv[argc++] = av[i];
993 argv[argc] = NULL;
995 for (i = 0; argv[i]; i++)
996 d_print("ARG: '%s'\n", argv[i]);
998 run = 1;
999 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
1000 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
1001 info_msg("Aborted");
1002 run = 0;
1005 if (run) {
1006 int status;
1008 if (spawn(argv, &status)) {
1009 error_msg("executing %s: %s", argv[0], strerror(errno));
1010 } else {
1011 if (WIFEXITED(status)) {
1012 int rc = WEXITSTATUS(status);
1014 if (rc)
1015 error_msg("%s returned %d", argv[0], rc);
1017 if (WIFSIGNALED(status))
1018 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1020 switch (cur_view) {
1021 case TREE_VIEW:
1022 case SORTED_VIEW:
1023 /* this must be done before sel_tis are unreffed */
1024 free_str_array(av);
1025 free(argv);
1027 /* remove non-existed files, update tags for changed files */
1028 cmus_update_tis(sel_tis, sel_tis_nr);
1030 /* we don't own sel_tis anymore! */
1031 return;
1035 free_str_array(av);
1036 free(argv);
1037 for (i = 0; sel_tis[i]; i++)
1038 track_info_unref(sel_tis[i]);
1039 free(sel_tis);
1042 static int get_one_ti(void *data, struct track_info *ti)
1044 struct track_info **sel_ti = data;
1046 track_info_ref(ti);
1047 *sel_ti = ti;
1048 /* stop the for each loop, we need only the first selected track */
1049 return 1;
1052 static void cmd_echo(char *arg)
1054 struct track_info *sel_ti;
1055 char *ptr = arg;
1057 while (1) {
1058 ptr = strchr(ptr, '{');
1059 if (ptr == NULL)
1060 break;
1061 if (ptr[1] == '}')
1062 break;
1063 ptr++;
1066 if (ptr == NULL) {
1067 info_msg("%s", arg);
1068 return;
1071 if (cur_view > QUEUE_VIEW) {
1072 info_msg("echo with {} in its arguments is supported only in views 1-4");
1073 return;
1076 *ptr = 0;
1077 ptr += 2;
1079 /* get only the first selected track */
1080 sel_ti = NULL;
1082 editable_lock();
1083 switch (cur_view) {
1084 case TREE_VIEW:
1085 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1086 break;
1087 case SORTED_VIEW:
1088 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1089 break;
1090 case PLAYLIST_VIEW:
1091 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1092 break;
1093 case QUEUE_VIEW:
1094 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1095 break;
1097 editable_unlock();
1099 if (sel_ti == NULL)
1100 return;
1102 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1103 track_info_unref(sel_ti);
1106 #define VF_RELATIVE 0x01
1107 #define VF_PERCENTAGE 0x02
1109 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1111 unsigned int f = 0;
1112 int ch, val = 0, digits = 0, sign = 1;
1114 if (*arg == '-') {
1115 arg++;
1116 f |= VF_RELATIVE;
1117 sign = -1;
1118 } else if (*arg == '+') {
1119 arg++;
1120 f |= VF_RELATIVE;
1123 while (1) {
1124 ch = *arg++;
1125 if (ch < '0' || ch > '9')
1126 break;
1127 val *= 10;
1128 val += ch - '0';
1129 digits++;
1131 if (digits == 0)
1132 goto err;
1134 if (ch == '%') {
1135 f |= VF_PERCENTAGE;
1136 ch = *arg;
1138 if (ch)
1139 goto err;
1141 *value = sign * val;
1142 *flags = f;
1143 return 0;
1144 err:
1145 return -1;
1148 static int calc_vol(int val, int old, unsigned int flags)
1150 if (flags & VF_RELATIVE) {
1151 if (flags & VF_PERCENTAGE)
1152 val = scale_from_percentage(val, volume_max);
1153 val += old;
1154 } else if (flags & VF_PERCENTAGE) {
1155 val = scale_from_percentage(val, volume_max);
1157 return clamp(val, 0, volume_max);
1161 * :vol value [value]
1163 * where value is [-+]?[0-9]+%?
1165 static void cmd_vol(char *arg)
1167 char **values = get_words(arg);
1168 unsigned int lf, rf;
1169 int l, r, ol, or;
1171 if (values[1] && values[2])
1172 goto err;
1174 if (parse_vol_arg(values[0], &l, &lf))
1175 goto err;
1177 r = l;
1178 rf = lf;
1179 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1180 goto err;
1182 free_str_array(values);
1184 player_get_volume(&ol, &or);
1185 l = calc_vol(l, ol, lf);
1186 r = calc_vol(r, or, rf);
1187 player_set_volume(l, r);
1188 return;
1189 err:
1190 free_str_array(values);
1191 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1194 static void cmd_prev_view(char *arg)
1196 int tmp;
1197 if (prev_view >= 0) {
1198 tmp = cur_view;
1199 set_view(prev_view);
1200 prev_view = tmp;
1204 static void cmd_view(char *arg)
1206 int view;
1208 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view) && (view - 1) != cur_view) {
1209 prev_view = cur_view;
1210 set_view(view - 1);
1214 static void cmd_p_next(char *arg)
1216 cmus_next();
1219 static void cmd_p_pause(char *arg)
1221 player_pause();
1224 static void cmd_p_play(char *arg)
1226 if (arg) {
1227 cmus_play_file(arg);
1228 } else {
1229 player_play();
1233 static void cmd_p_prev(char *arg)
1235 cmus_prev();
1238 static void cmd_p_stop(char *arg)
1240 player_stop();
1243 static void cmd_search_next(char *arg)
1245 if (search_str) {
1246 if (!search_next(searchable, search_str, search_direction))
1247 search_not_found();
1251 static void cmd_search_prev(char *arg)
1253 if (search_str) {
1254 if (!search_next(searchable, search_str, !search_direction))
1255 search_not_found();
1259 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1261 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1264 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1266 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1269 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1271 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1274 static for_each_sel_ti_cb view_for_each_sel[4] = {
1275 tree_for_each_sel,
1276 sorted_for_each_sel,
1277 pl_for_each_sel,
1278 pq_for_each_sel
1281 /* wrapper for void lib_add_track(struct track_info *) etc. */
1282 static int wrapper_cb(void *data, struct track_info *ti)
1284 add_ti_cb add = data;
1286 add(ti);
1287 return 0;
1290 static void add_from_browser(add_ti_cb add, int job_type)
1292 char *sel = browser_get_sel();
1294 if (sel) {
1295 enum file_type ft;
1296 char *ret;
1298 ft = cmus_detect_ft(sel, &ret);
1299 if (ft != FILE_TYPE_INVALID) {
1300 cmus_add(add, ret, ft, job_type);
1301 window_down(browser_win, 1);
1303 free(ret);
1304 free(sel);
1308 static void cmd_win_add_l(char *arg)
1310 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1311 return;
1313 if (cur_view <= QUEUE_VIEW) {
1314 editable_lock();
1315 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1316 editable_unlock();
1317 } else if (cur_view == BROWSER_VIEW) {
1318 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1322 static void cmd_win_add_p(char *arg)
1324 /* could allow adding dups? */
1325 if (cur_view == PLAYLIST_VIEW)
1326 return;
1328 if (cur_view <= QUEUE_VIEW) {
1329 editable_lock();
1330 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1331 editable_unlock();
1332 } else if (cur_view == BROWSER_VIEW) {
1333 add_from_browser(pl_add_track, JOB_TYPE_PL);
1337 static void cmd_win_add_Q(char *arg)
1339 if (cur_view == QUEUE_VIEW)
1340 return;
1342 if (cur_view <= QUEUE_VIEW) {
1343 editable_lock();
1344 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1345 editable_unlock();
1346 } else if (cur_view == BROWSER_VIEW) {
1347 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1351 static void cmd_win_add_q(char *arg)
1353 if (cur_view == QUEUE_VIEW)
1354 return;
1356 if (cur_view <= QUEUE_VIEW) {
1357 editable_lock();
1358 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1359 editable_unlock();
1360 } else if (cur_view == BROWSER_VIEW) {
1361 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1365 static void cmd_win_activate(char *arg)
1367 struct track_info *info = NULL;
1369 editable_lock();
1370 switch (cur_view) {
1371 case TREE_VIEW:
1372 info = tree_set_selected();
1373 break;
1374 case SORTED_VIEW:
1375 info = sorted_set_selected();
1376 break;
1377 case PLAYLIST_VIEW:
1378 info = pl_set_selected();
1379 break;
1380 case QUEUE_VIEW:
1381 break;
1382 case BROWSER_VIEW:
1383 browser_enter();
1384 break;
1385 case FILTERS_VIEW:
1386 filters_activate();
1387 break;
1388 case HELP_VIEW:
1389 help_select();
1390 break;
1392 editable_unlock();
1394 if (info) {
1395 /* update lib/pl mode */
1396 if (cur_view < 2)
1397 play_library = 1;
1398 if (cur_view == 2)
1399 play_library = 0;
1401 player_play_file(info);
1405 static void cmd_win_mv_after(char *arg)
1407 editable_lock();
1408 switch (cur_view) {
1409 case TREE_VIEW:
1410 break;
1411 case SORTED_VIEW:
1412 editable_move_after(&lib_editable);
1413 break;
1414 case PLAYLIST_VIEW:
1415 editable_move_after(&pl_editable);
1416 break;
1417 case QUEUE_VIEW:
1418 editable_move_after(&pq_editable);
1419 break;
1420 case BROWSER_VIEW:
1421 break;
1422 case FILTERS_VIEW:
1423 break;
1424 case HELP_VIEW:
1425 break;
1427 editable_unlock();
1430 static void cmd_win_mv_before(char *arg)
1432 editable_lock();
1433 switch (cur_view) {
1434 case TREE_VIEW:
1435 break;
1436 case SORTED_VIEW:
1437 editable_move_before(&lib_editable);
1438 break;
1439 case PLAYLIST_VIEW:
1440 editable_move_before(&pl_editable);
1441 break;
1442 case QUEUE_VIEW:
1443 editable_move_before(&pq_editable);
1444 break;
1445 case BROWSER_VIEW:
1446 break;
1447 case FILTERS_VIEW:
1448 break;
1449 case HELP_VIEW:
1450 break;
1452 editable_unlock();
1455 static void cmd_win_remove(char *arg)
1457 editable_lock();
1458 switch (cur_view) {
1459 case TREE_VIEW:
1460 tree_remove_sel();
1461 break;
1462 case SORTED_VIEW:
1463 editable_remove_sel(&lib_editable);
1464 break;
1465 case PLAYLIST_VIEW:
1466 editable_remove_sel(&pl_editable);
1467 break;
1468 case QUEUE_VIEW:
1469 editable_remove_sel(&pq_editable);
1470 break;
1471 case BROWSER_VIEW:
1472 browser_delete();
1473 break;
1474 case FILTERS_VIEW:
1475 filters_delete_filter();
1476 break;
1477 case HELP_VIEW:
1478 help_remove();
1479 break;
1481 editable_unlock();
1484 static void cmd_win_sel_cur(char *arg)
1486 editable_lock();
1487 switch (cur_view) {
1488 case TREE_VIEW:
1489 tree_sel_current();
1490 break;
1491 case SORTED_VIEW:
1492 sorted_sel_current();
1493 break;
1494 case PLAYLIST_VIEW:
1495 pl_sel_current();
1496 break;
1497 case QUEUE_VIEW:
1498 break;
1499 case BROWSER_VIEW:
1500 break;
1501 case FILTERS_VIEW:
1502 break;
1503 case HELP_VIEW:
1504 break;
1506 editable_unlock();
1509 static void cmd_win_toggle(char *arg)
1511 switch (cur_view) {
1512 case TREE_VIEW:
1513 editable_lock();
1514 tree_toggle_expand_artist();
1515 editable_unlock();
1516 break;
1517 case SORTED_VIEW:
1518 editable_lock();
1519 editable_toggle_mark(&lib_editable);
1520 editable_unlock();
1521 break;
1522 case PLAYLIST_VIEW:
1523 editable_lock();
1524 editable_toggle_mark(&pl_editable);
1525 editable_unlock();
1526 break;
1527 case QUEUE_VIEW:
1528 editable_lock();
1529 editable_toggle_mark(&pq_editable);
1530 editable_unlock();
1531 break;
1532 case BROWSER_VIEW:
1533 break;
1534 case FILTERS_VIEW:
1535 filters_toggle_filter();
1536 break;
1537 case HELP_VIEW:
1538 help_toggle();
1539 break;
1543 static struct window *current_win(void)
1545 switch (cur_view) {
1546 case TREE_VIEW:
1547 return lib_cur_win;
1548 case SORTED_VIEW:
1549 return lib_editable.win;
1550 case PLAYLIST_VIEW:
1551 return pl_editable.win;
1552 case QUEUE_VIEW:
1553 return pq_editable.win;
1554 case BROWSER_VIEW:
1555 return browser_win;
1556 case HELP_VIEW:
1557 return help_win;
1558 case FILTERS_VIEW:
1559 default:
1560 return filters_win;
1564 static void cmd_win_bottom(char *arg)
1566 editable_lock();
1567 window_goto_bottom(current_win());
1568 editable_unlock();
1571 static void cmd_win_down(char *arg)
1573 editable_lock();
1574 window_down(current_win(), 1);
1575 editable_unlock();
1578 static void cmd_win_next(char *arg)
1580 if (cur_view == TREE_VIEW) {
1581 editable_lock();
1582 tree_toggle_active_window();
1583 editable_unlock();
1587 static void cmd_win_pg_down(char *arg)
1589 editable_lock();
1590 window_page_down(current_win());
1591 editable_unlock();
1594 static void cmd_win_pg_up(char *arg)
1596 editable_lock();
1597 window_page_up(current_win());
1598 editable_unlock();
1601 static void cmd_win_top(char *arg)
1603 editable_lock();
1604 window_goto_top(current_win());
1605 editable_unlock();
1608 static void cmd_win_up(char *arg)
1610 editable_lock();
1611 window_up(current_win(), 1);
1612 editable_unlock();
1615 static void cmd_win_update(char *arg)
1617 switch (cur_view) {
1618 case TREE_VIEW:
1619 case SORTED_VIEW:
1620 cmus_update_lib();
1621 break;
1622 case BROWSER_VIEW:
1623 browser_reload();
1624 break;
1628 static void cmd_browser_up(char *arg)
1630 browser_up();
1633 static void cmd_refresh(char *arg)
1635 clearok(curscr, TRUE);
1636 refresh();
1639 static int cmp_intp(const void *ap, const void *bp)
1641 int a = *(int *)ap;
1642 int b = *(int *)bp;
1643 return a - b;
1646 static int *rand_array(int size, int nmax)
1648 int *r = xnew(int, size + 1);
1649 int i, offset = 0;
1650 int count = size;
1652 if (count > nmax / 2) {
1654 * Imagine that there are 1000 tracks in library and we want to
1655 * add 998 random tracks to queue. After we have added 997
1656 * random numbers to the array it would be quite hard to find a
1657 * random number that isn't already in the array (3/1000
1658 * probability).
1660 * So we invert the logic:
1662 * Find two (1000 - 998) random numbers in 0..999 range and put
1663 * them at end of the array. Sort the numbers and then fill
1664 * the array starting at index 0 with incrementing values that
1665 * are not in the set of random numbers.
1667 count = nmax - count;
1668 offset = size - count;
1671 for (i = 0; i < count; ) {
1672 int v, j;
1673 found:
1674 v = rand() % nmax;
1675 for (j = 0; j < i; j++) {
1676 if (r[offset + j] == v)
1677 goto found;
1679 r[offset + i++] = v;
1681 qsort(r + offset, count, sizeof(*r), cmp_intp);
1683 if (offset) {
1684 int j, n;
1686 /* simplifies next loop */
1687 r[size] = nmax;
1689 /* convert the indexes we don't want to those we want */
1690 i = 0;
1691 j = offset;
1692 n = 0;
1693 do {
1694 while (n < r[j])
1695 r[i++] = n++;
1696 j++;
1697 n++;
1698 } while (i < size);
1700 return r;
1703 static int count_albums(void)
1705 struct artist *artist;
1706 struct list_head *item;
1707 int count = 0;
1709 list_for_each_entry(artist, &lib_artist_head, node) {
1710 list_for_each(item, &artist->album_head)
1711 count++;
1713 return count;
1716 struct album_list {
1717 struct list_head node;
1718 const struct album *album;
1721 static void cmd_lqueue(char *arg)
1723 LIST_HEAD(head);
1724 const struct list_head *item;
1725 const struct album *album;
1726 int count = 1, nmax, i, pos;
1727 int *r;
1729 if (arg) {
1730 long int val;
1732 if (str_to_int(arg, &val) || val <= 0) {
1733 error_msg("argument must be positive integer");
1734 return;
1736 count = val;
1738 editable_lock();
1739 nmax = count_albums();
1740 if (count > nmax)
1741 count = nmax;
1742 if (!count)
1743 goto unlock;
1745 r = rand_array(count, nmax);
1746 album = to_album(to_artist(lib_artist_head.next)->album_head.next);
1747 pos = 0;
1748 for (i = 0; i < count; i++) {
1749 struct album_list *a;
1751 while (pos < r[i]) {
1752 struct artist *artist = album->artist;
1753 if (album->node.next == &artist->album_head) {
1754 artist = to_artist(artist->node.next);
1755 album = to_album(artist->album_head.next);
1756 } else {
1757 album = to_album(album->node.next);
1759 pos++;
1761 a = xnew(struct album_list, 1);
1762 a->album = album;
1763 list_add_rand(&head, &a->node, i);
1765 free(r);
1767 item = head.next;
1768 do {
1769 struct list_head *next = item->next;
1770 struct album_list *a = container_of(item, struct album_list, node);
1771 struct tree_track *t;
1773 list_for_each_entry(t, &a->album->track_head, node)
1774 editable_add(&pq_editable, simple_track_new(tree_track_info(t)));
1775 free(a);
1776 item = next;
1777 } while (item != &head);
1778 unlock:
1779 editable_unlock();
1782 static void cmd_tqueue(char *arg)
1784 LIST_HEAD(head);
1785 struct list_head *item;
1786 int count = 1, i, pos;
1787 int *r;
1789 if (arg) {
1790 long int val;
1792 if (str_to_int(arg, &val) || val <= 0) {
1793 error_msg("argument must be positive integer");
1794 return;
1796 count = val;
1798 editable_lock();
1799 if (count > lib_editable.nr_tracks)
1800 count = lib_editable.nr_tracks;
1801 if (!count)
1802 goto unlock;
1804 r = rand_array(count, lib_editable.nr_tracks);
1805 item = lib_editable.head.next;
1806 pos = 0;
1807 for (i = 0; i < count; i++) {
1808 struct simple_track *t;
1810 while (pos < r[i]) {
1811 item = item->next;
1812 pos++;
1814 t = simple_track_new(to_simple_track(item)->info);
1815 list_add_rand(&head, &t->node, i);
1817 free(r);
1819 item = head.next;
1820 do {
1821 struct list_head *next = item->next;
1822 struct simple_track *t = to_simple_track(item);
1823 editable_add(&pq_editable, t);
1824 item = next;
1825 } while (item != &head);
1826 unlock:
1827 editable_unlock();
1830 /* tab exp {{{
1832 * these functions fill tabexp struct, which is resetted beforehand
1835 /* buffer used for tab expansion */
1836 static char expbuf[512];
1838 static int filter_directories(const char *name, const struct stat *s)
1840 return S_ISDIR(s->st_mode);
1843 static int filter_any(const char *name, const struct stat *s)
1845 return 1;
1848 static int filter_playable(const char *name, const struct stat *s)
1850 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1853 static int filter_playlist(const char *name, const struct stat *s)
1855 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1858 static int filter_supported(const char *name, const struct stat *s)
1860 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1863 static void expand_files(const char *str)
1865 expand_files_and_dirs(str, filter_any);
1868 static void expand_directories(const char *str)
1870 expand_files_and_dirs(str, filter_directories);
1873 static void expand_playable(const char *str)
1875 expand_files_and_dirs(str, filter_playable);
1878 static void expand_playlist(const char *str)
1880 expand_files_and_dirs(str, filter_playlist);
1883 static void expand_supported(const char *str)
1885 expand_files_and_dirs(str, filter_supported);
1888 static void expand_add(const char *str)
1890 int flag = parse_flags(&str, "lpqQ");
1892 if (flag == -1)
1893 return;
1894 if (str == NULL)
1895 str = "";
1896 expand_supported(str);
1898 if (tabexp.head && flag) {
1899 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1900 free(tabexp.head);
1901 tabexp.head = xstrdup(expbuf);
1905 static void expand_load_save(const char *str)
1907 int flag = parse_flags(&str, "lp");
1909 if (flag == -1)
1910 return;
1911 if (str == NULL)
1912 str = "";
1913 expand_playlist(str);
1915 if (tabexp.head && flag) {
1916 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1917 free(tabexp.head);
1918 tabexp.head = xstrdup(expbuf);
1922 static void expand_key_context(const char *str, const char *force)
1924 int pos, i, len = strlen(str);
1925 char **tails;
1927 tails = xnew(char *, NR_CTXS + 1);
1928 pos = 0;
1929 for (i = 0; key_context_names[i]; i++) {
1930 int cmp = strncmp(str, key_context_names[i], len);
1931 if (cmp > 0)
1932 continue;
1933 if (cmp < 0)
1934 break;
1935 tails[pos++] = xstrdup(key_context_names[i] + len);
1938 if (pos == 0) {
1939 free(tails);
1940 return;
1942 if (pos == 1) {
1943 char *tmp = xstrjoin(tails[0], " ");
1944 free(tails[0]);
1945 tails[0] = tmp;
1947 tails[pos] = NULL;
1948 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1949 tabexp.head = xstrdup(expbuf);
1950 tabexp.tails = tails;
1953 static int get_context(const char *str, int len)
1955 int i, c = -1, count = 0;
1957 for (i = 0; key_context_names[i]; i++) {
1958 if (strncmp(str, key_context_names[i], len) == 0) {
1959 if (key_context_names[i][len] == 0) {
1960 /* exact */
1961 return i;
1963 c = i;
1964 count++;
1967 if (count == 1)
1968 return c;
1969 return -1;
1972 static void expand_command_line(const char *str);
1974 static void expand_bind_args(const char *str)
1976 /* :bind context key function
1978 * possible values for str:
1980 * context k
1981 * context key f
1983 * you need to know context before you can expand function
1985 /* start and end pointers for context, key and function */
1986 const char *cs, *ce, *ks, *ke, *fs;
1987 int i, c, k, count;
1988 int flag = parse_flags((const char **)&str, "f");
1989 const char *force = "";
1991 if (flag == -1)
1992 return;
1993 if (str == NULL)
1994 str = "";
1996 if (flag == 'f')
1997 force = "-f ";
1999 cs = str;
2000 ce = strchr(cs, ' ');
2001 if (ce == NULL) {
2002 expand_key_context(cs, force);
2003 return;
2006 /* context must be expandable */
2007 c = get_context(cs, ce - cs);
2008 if (c == -1) {
2009 /* context is ambiguous or invalid */
2010 return;
2013 ks = ce;
2014 while (*ks == ' ')
2015 ks++;
2016 ke = strchr(ks, ' ');
2017 if (ke == NULL) {
2018 /* expand key */
2019 int len = strlen(ks);
2020 PTR_ARRAY(array);
2022 for (i = 0; key_table[i].name; i++) {
2023 int cmp = strncmp(ks, key_table[i].name, len);
2024 if (cmp > 0)
2025 continue;
2026 if (cmp < 0)
2027 break;
2028 ptr_array_add(&array, xstrdup(key_table[i].name + len));
2031 if (!array.count)
2032 return;
2034 if (array.count == 1) {
2035 char **ptrs = array.ptrs;
2036 char *tmp = xstrjoin(ptrs[0], " ");
2037 free(ptrs[0]);
2038 ptrs[0] = tmp;
2041 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
2043 ptr_array_plug(&array);
2044 tabexp.head = xstrdup(expbuf);
2045 tabexp.tails = array.ptrs;
2046 return;
2049 /* key must be expandable */
2050 k = -1;
2051 count = 0;
2052 for (i = 0; key_table[i].name; i++) {
2053 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
2054 if (key_table[i].name[ke - ks] == 0) {
2055 /* exact */
2056 k = i;
2057 count = 1;
2058 break;
2060 k = i;
2061 count++;
2064 if (count != 1) {
2065 /* key is ambiguous or invalid */
2066 return;
2069 fs = ke;
2070 while (*fs == ' ')
2071 fs++;
2073 if (*fs == ':')
2074 fs++;
2076 /* expand com [arg...] */
2077 expand_command_line(fs);
2078 if (tabexp.head == NULL) {
2079 /* command expand failed */
2080 return;
2084 * tabexp.head is now "com"
2085 * tabexp.tails is [ mand1 mand2 ... ]
2087 * need to change tabexp.head to "context key com"
2090 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
2091 key_table[k].name, tabexp.head);
2092 free(tabexp.head);
2093 tabexp.head = xstrdup(expbuf);
2096 static void expand_unbind_args(const char *str)
2098 /* :unbind context key */
2099 /* start and end pointers for context and key */
2100 const char *cs, *ce, *ks;
2101 const struct binding *b;
2102 PTR_ARRAY(array);
2103 int c, len;
2105 cs = str;
2106 ce = strchr(cs, ' ');
2107 if (ce == NULL) {
2108 expand_key_context(cs, "");
2109 return;
2112 /* context must be expandable */
2113 c = get_context(cs, ce - cs);
2114 if (c == -1) {
2115 /* context is ambiguous or invalid */
2116 return;
2119 ks = ce;
2120 while (*ks == ' ')
2121 ks++;
2123 /* expand key */
2124 len = strlen(ks);
2125 b = key_bindings[c];
2126 while (b) {
2127 if (!strncmp(ks, b->key->name, len))
2128 ptr_array_add(&array, xstrdup(b->key->name + len));
2129 b = b->next;
2131 if (!array.count)
2132 return;
2134 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
2136 ptr_array_plug(&array);
2137 tabexp.head = xstrdup(expbuf);
2138 tabexp.tails = array.ptrs;
2141 static void expand_factivate(const char *str)
2143 /* "name1 name2 name3", expand only name3 */
2144 struct filter_entry *e;
2145 const char *name;
2146 PTR_ARRAY(array);
2147 int str_len, len, i;
2149 str_len = strlen(str);
2150 i = str_len;
2151 while (i > 0) {
2152 if (str[i - 1] == ' ')
2153 break;
2154 i--;
2156 len = str_len - i;
2157 name = str + i;
2159 list_for_each_entry(e, &filters_head, node) {
2160 if (!strncmp(name, e->name, len))
2161 ptr_array_add(&array, xstrdup(e->name + len));
2163 if (!array.count)
2164 return;
2166 ptr_array_plug(&array);
2167 tabexp.head = xstrdup(str);
2168 tabexp.tails = array.ptrs;
2171 static void expand_options(const char *str)
2173 struct cmus_opt *opt;
2174 int len;
2175 char **tails;
2177 /* tabexp is resetted */
2178 len = strlen(str);
2179 if (len > 1 && str[len - 1] == '=') {
2180 /* expand value */
2181 char *var = xstrndup(str, len - 1);
2183 list_for_each_entry(opt, &option_head, node) {
2184 if (strcmp(var, opt->name) == 0) {
2185 char buf[OPTION_MAX_SIZE];
2187 tails = xnew(char *, 2);
2189 buf[0] = 0;
2190 opt->get(opt->id, buf);
2191 tails[0] = xstrdup(buf);
2192 tails[1] = NULL;
2194 tabexp.head = xstrdup(str);
2195 tabexp.tails = tails;
2196 free(var);
2197 return;
2200 free(var);
2201 } else {
2202 /* expand variable */
2203 int pos;
2205 tails = xnew(char *, nr_options + 1);
2206 pos = 0;
2207 list_for_each_entry(opt, &option_head, node) {
2208 if (strncmp(str, opt->name, len) == 0)
2209 tails[pos++] = xstrdup(opt->name + len);
2211 if (pos > 0) {
2212 if (pos == 1) {
2213 /* only one variable matches, add '=' */
2214 char *tmp = xstrjoin(tails[0], "=");
2216 free(tails[0]);
2217 tails[0] = tmp;
2220 tails[pos] = NULL;
2221 tabexp.head = xstrdup(str);
2222 tabexp.tails = tails;
2223 } else {
2224 free(tails);
2229 static void expand_toptions(const char *str)
2231 struct cmus_opt *opt;
2232 int len, pos;
2233 char **tails;
2235 tails = xnew(char *, nr_options + 1);
2236 len = strlen(str);
2237 pos = 0;
2238 list_for_each_entry(opt, &option_head, node) {
2239 if (opt->toggle == NULL)
2240 continue;
2241 if (strncmp(str, opt->name, len) == 0)
2242 tails[pos++] = xstrdup(opt->name + len);
2244 if (pos > 0) {
2245 tails[pos] = NULL;
2246 tabexp.head = xstrdup(str);
2247 tabexp.tails = tails;
2248 } else {
2249 free(tails);
2253 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2255 struct directory dir;
2256 const char *name, *dot;
2257 int len = strlen(str);
2259 if (dir_open(&dir, dirname))
2260 return;
2262 while ((name = dir_read(&dir))) {
2263 if (!S_ISREG(dir.st.st_mode))
2264 continue;
2265 if (strncmp(name, str, len))
2266 continue;
2267 dot = strrchr(name, '.');
2268 if (dot == NULL || strcmp(dot, ".theme"))
2269 continue;
2270 if (dot - name < len)
2271 /* str is "foo.th"
2272 * matches "foo.theme"
2273 * which also ends with ".theme"
2275 continue;
2276 ptr_array_add(array, xstrndup(name + len, dot - name - len));
2278 dir_close(&dir);
2281 static void expand_colorscheme(const char *str)
2283 PTR_ARRAY(array);
2285 load_themes(cmus_config_dir, str, &array);
2286 load_themes(DATADIR "/cmus", str, &array);
2288 if (array.count) {
2289 ptr_array_sort(&array, strptrcmp);
2291 ptr_array_plug(&array);
2292 tabexp.head = xstrdup(str);
2293 tabexp.tails = array.ptrs;
2297 /* tab exp }}} */
2299 /* sort by name */
2300 struct command commands[] = {
2301 { "add", cmd_add, 1, 1, expand_add, 0, 0 },
2302 { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
2303 { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
2304 { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
2305 { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
2306 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
2307 { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
2308 { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
2309 { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
2310 { "fset", cmd_fset, 1, 1, NULL, 0, 0 },
2311 { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
2312 { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
2313 { "lqueue", cmd_lqueue, 0, 1, NULL, 0, 0 },
2314 { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
2315 { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
2316 { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
2317 { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
2318 { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
2319 { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
2320 { "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
2321 { "quit", cmd_quit, 0, 0, NULL, 0, 0 },
2322 { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
2323 { "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
2324 { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
2325 { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
2326 { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
2327 { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
2328 { "set", cmd_set, 1, 1, expand_options, 0, 0 },
2329 { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
2330 { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
2331 { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
2332 { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
2333 { "tqueue", cmd_tqueue, 0, 1, NULL, 0, 0 },
2334 { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
2335 { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
2336 { "view", cmd_view, 1, 1, NULL, 0, 0 },
2337 { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
2338 { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
2339 { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
2340 { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
2341 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
2342 { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
2343 { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
2344 { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
2345 { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
2346 { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
2347 { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
2348 { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
2349 { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
2350 { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
2351 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
2352 { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
2353 { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
2354 { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
2355 { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
2356 { NULL, NULL, 0, 0, 0, 0, 0 }
2359 /* fills tabexp struct */
2360 static void expand_commands(const char *str)
2362 int i, len, pos;
2363 char **tails;
2365 /* tabexp is resetted */
2366 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2367 len = strlen(str);
2368 pos = 0;
2369 for (i = 0; commands[i].name; i++) {
2370 if (strncmp(str, commands[i].name, len) == 0)
2371 tails[pos++] = xstrdup(commands[i].name + len);
2373 if (pos > 0) {
2374 if (pos == 1) {
2375 /* only one command matches, add ' ' */
2376 char *tmp = xstrjoin(tails[0], " ");
2378 free(tails[0]);
2379 tails[0] = tmp;
2381 tails[pos] = NULL;
2382 tabexp.head = xstrdup(str);
2383 tabexp.tails = tails;
2384 } else {
2385 free(tails);
2389 struct command *get_command(const char *str)
2391 int i, len;
2393 while (*str == ' ')
2394 str++;
2395 for (len = 0; str[len] && str[len] != ' '; len++)
2398 for (i = 0; commands[i].name; i++) {
2399 if (strncmp(str, commands[i].name, len))
2400 continue;
2402 if (commands[i].name[len] == 0) {
2403 /* exact */
2404 return &commands[i];
2407 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2408 /* ambiguous */
2409 return NULL;
2411 return &commands[i];
2413 return NULL;
2416 /* fills tabexp struct */
2417 static void expand_command_line(const char *str)
2419 /* :command [arg]...
2421 * examples:
2423 * str expanded value (tabexp.head)
2424 * -------------------------------------
2425 * fs fset
2426 * b c bind common
2427 * se se (tabexp.tails = [ ek t ])
2429 /* command start/end, argument start */
2430 const char *cs, *ce, *as;
2431 const struct command *cmd;
2433 cs = str;
2434 ce = strchr(cs, ' ');
2435 if (ce == NULL) {
2436 /* expand command */
2437 expand_commands(cs);
2438 return;
2441 /* command must be expandable */
2442 cmd = get_command(cs);
2443 if (cmd == NULL) {
2444 /* command ambiguous or invalid */
2445 return;
2448 if (cmd->expand == NULL) {
2449 /* can't expand argument */
2450 return;
2453 as = ce;
2454 while (*as == ' ')
2455 as++;
2457 /* expand argument */
2458 cmd->expand(as);
2459 if (tabexp.head == NULL) {
2460 /* argument expansion failed */
2461 return;
2464 /* tabexp.head is now start of the argument string */
2465 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2466 free(tabexp.head);
2467 tabexp.head = xstrdup(expbuf);
2470 static void tab_expand(void)
2472 char *s1, *s2, *tmp;
2473 int pos;
2475 /* strip white space */
2476 pos = 0;
2477 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2478 pos++;
2480 /* string to expand */
2481 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2483 /* tail */
2484 s2 = xstrdup(cmdline.line + cmdline.bpos);
2486 tmp = tabexp_expand(s1, expand_command_line);
2487 if (tmp) {
2488 /* tmp.s2 */
2489 int l1, l2;
2491 l1 = strlen(tmp);
2492 l2 = strlen(s2);
2493 cmdline.blen = l1 + l2;
2494 if (cmdline.blen >= cmdline.size) {
2495 while (cmdline.blen >= cmdline.size)
2496 cmdline.size *= 2;
2497 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2499 sprintf(cmdline.line, "%s%s", tmp, s2);
2500 cmdline.bpos = l1;
2501 cmdline.cpos = u_strlen(tmp);
2502 cmdline.clen = u_strlen(cmdline.line);
2503 free(tmp);
2505 free(s1);
2506 free(s2);
2509 static void reset_tab_expansion(void)
2511 tabexp_reset();
2512 arg_expand_cmd = -1;
2515 int run_only_safe_commands;
2517 /* FIXME: parse all arguments */
2518 void run_command(const char *buf)
2520 char *cmd, *arg;
2521 int cmd_start, cmd_end, cmd_len;
2522 int arg_start, arg_end;
2523 int i;
2525 i = 0;
2526 while (buf[i] && buf[i] == ' ')
2527 i++;
2529 if (buf[i] == '#')
2530 return;
2532 cmd_start = i;
2533 while (buf[i] && buf[i] != ' ')
2534 i++;
2535 cmd_end = i;
2536 while (buf[i] && buf[i] == ' ')
2537 i++;
2538 arg_start = i;
2539 while (buf[i])
2540 i++;
2541 arg_end = i;
2543 cmd_len = cmd_end - cmd_start;
2544 if (cmd_len == 0)
2545 return;
2547 cmd = xstrndup(buf + cmd_start, cmd_len);
2548 if (arg_start == arg_end) {
2549 arg = NULL;
2550 } else {
2551 arg = xstrndup(buf + arg_start, arg_end - arg_start);
2553 i = 0;
2554 while (1) {
2555 const struct command *c = &commands[i];
2557 if (c->name == NULL) {
2558 error_msg("unknown command\n");
2559 break;
2561 if (strncmp(cmd, c->name, cmd_len) == 0) {
2562 const char *next = commands[i + 1].name;
2563 int exact = c->name[cmd_len] == 0;
2565 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2566 error_msg("ambiguous command\n");
2567 break;
2569 if (c->min_args > 0 && arg == NULL) {
2570 error_msg("not enough arguments\n");
2571 break;
2573 if (c->max_args == 0 && arg) {
2574 error_msg("too many arguments\n");
2575 break;
2577 if (run_only_safe_commands && c->flags & CMD_UNSAFE) {
2578 d_print("trying to execute unsafe command over net\n");
2579 break;
2581 c->func(arg);
2582 break;
2584 i++;
2586 free(arg);
2587 free(cmd);
2590 static void reset_history_search(void)
2592 history_reset_search(&cmd_history);
2593 free(history_search_text);
2594 history_search_text = NULL;
2597 static void backspace(void)
2599 if (cmdline.clen > 0) {
2600 cmdline_backspace();
2601 } else {
2602 input_mode = NORMAL_MODE;
2606 void command_mode_ch(uchar ch)
2608 switch (ch) {
2609 case 0x01: // ^A
2610 cmdline_move_home();
2611 break;
2612 case 0x02: // ^B
2613 cmdline_move_left();
2614 break;
2615 case 0x04: // ^D
2616 cmdline_delete_ch();
2617 break;
2618 case 0x05: // ^E
2619 cmdline_move_end();
2620 break;
2621 case 0x06: // ^F
2622 cmdline_move_right();
2623 break;
2624 case 0x03: // ^C
2625 case 0x07: // ^G
2626 case 0x1B: // ESC
2627 if (cmdline.blen) {
2628 history_add_line(&cmd_history, cmdline.line);
2629 cmdline_clear();
2631 input_mode = NORMAL_MODE;
2632 break;
2633 case 0x0A:
2634 if (cmdline.blen) {
2635 run_command(cmdline.line);
2636 history_add_line(&cmd_history, cmdline.line);
2637 cmdline_clear();
2639 input_mode = NORMAL_MODE;
2640 break;
2641 case 0x0B:
2642 cmdline_clear_end();
2643 break;
2644 case 0x09:
2645 tab_expand();
2646 break;
2647 case 0x15:
2648 cmdline_backspace_to_bol();
2649 break;
2650 case 127:
2651 backspace();
2652 break;
2653 default:
2654 cmdline_insert_ch(ch);
2656 reset_history_search();
2657 if (ch != 0x09)
2658 reset_tab_expansion();
2661 void command_mode_key(int key)
2663 reset_tab_expansion();
2664 switch (key) {
2665 case KEY_DC:
2666 cmdline_delete_ch();
2667 break;
2668 case KEY_BACKSPACE:
2669 backspace();
2670 break;
2671 case KEY_LEFT:
2672 cmdline_move_left();
2673 return;
2674 case KEY_RIGHT:
2675 cmdline_move_right();
2676 return;
2677 case KEY_HOME:
2678 cmdline_move_home();
2679 return;
2680 case KEY_END:
2681 cmdline_move_end();
2682 return;
2683 case KEY_UP:
2685 const char *s;
2687 if (history_search_text == NULL)
2688 history_search_text = xstrdup(cmdline.line);
2689 s = history_search_forward(&cmd_history, history_search_text);
2690 if (s)
2691 cmdline_set_text(s);
2693 return;
2694 case KEY_DOWN:
2695 if (history_search_text) {
2696 const char *s;
2698 s = history_search_backward(&cmd_history, history_search_text);
2699 if (s) {
2700 cmdline_set_text(s);
2701 } else {
2702 cmdline_set_text(history_search_text);
2705 return;
2706 default:
2707 d_print("key = %c (%d)\n", key, key);
2709 reset_history_search();
2712 void commands_init(void)
2714 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2715 history_load(&cmd_history, cmd_history_filename, 2000);
2718 void commands_exit(void)
2720 history_save(&cmd_history);
2721 free(cmd_history_filename);
2722 tabexp_reset();