aac: Collect all interesting ID3 frames
[cmus.git] / command_mode.c
blob6d05678ebf8eb952c98da1596f070b441de5e81c
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_cd(char *arg)
596 if (arg) {
597 char *dir, *absolute;
599 dir = expand_filename(arg);
600 absolute = path_absolute(dir);
601 if (chdir(dir) == -1) {
602 error_msg("could not cd to '%s': %s", dir, strerror(errno));
603 } else {
604 browser_chdir(absolute);
606 free(absolute);
607 free(dir);
608 } else {
609 if (chdir(home_dir) == -1) {
610 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
611 } else {
612 browser_chdir(home_dir);
617 static void cmd_bind(char *arg)
619 int flag = parse_flags((const char **)&arg, "f");
620 char *key, *func;
622 if (flag == -1)
623 return;
625 if (arg == NULL)
626 goto err;
628 key = strchr(arg, ' ');
629 if (key == NULL)
630 goto err;
631 *key++ = 0;
632 while (*key == ' ')
633 key++;
635 func = strchr(key, ' ');
636 if (func == NULL)
637 goto err;
638 *func++ = 0;
639 while (*func == ' ')
640 func++;
641 if (*func == 0)
642 goto err;
644 key_bind(arg, key, func, flag == 'f');
645 return;
646 err:
647 error_msg("expecting 3 arguments (context, key and function)\n");
650 static void cmd_unbind(char *arg)
652 int flag = parse_flags((const char **)&arg, "f");
653 char *key;
655 if (flag == -1)
656 return;
658 if (arg == NULL)
659 goto err;
661 key = strchr(arg, ' ');
662 if (key == NULL)
663 goto err;
664 *key++ = 0;
665 while (*key == ' ')
666 key++;
667 if (*key == 0)
668 goto err;
670 /* FIXME: remove spaces at end */
672 key_unbind(arg, key, flag == 'f');
673 return;
674 err:
675 error_msg("expecting 2 arguments (context and key)\n");
678 static void cmd_showbind(char *arg)
680 char *key;
682 key = strchr(arg, ' ');
683 if (key == NULL)
684 goto err;
685 *key++ = 0;
686 while (*key == ' ')
687 key++;
688 if (*key == 0)
689 goto err;
691 /* FIXME: remove spaces at end */
693 show_binding(arg, key);
694 return;
695 err:
696 error_msg("expecting 2 arguments (context and key)\n");
699 static void cmd_quit(char *arg)
701 if (!worker_has_job(JOB_TYPE_ANY) || yes_no_query("Tracks are being added. Quit and truncate playlist(s)? [y/N]"))
702 cmus_running = 0;
705 static void cmd_reshuffle(char *arg)
707 editable_lock();
708 lib_reshuffle();
709 pl_reshuffle();
710 editable_unlock();
713 static void cmd_source(char *arg)
715 char *filename = expand_filename(arg);
717 if (source_file(filename) == -1)
718 error_msg("sourcing %s: %s", filename, strerror(errno));
719 free(filename);
722 static void cmd_colorscheme(char *arg)
724 char filename[512];
726 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
727 if (source_file(filename) == -1) {
728 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
729 if (source_file(filename) == -1)
730 error_msg("sourcing %s: %s", filename, strerror(errno));
735 * \" inside double-quotes becomes "
736 * \\ inside double-quotes becomes \
738 static char *parse_quoted(const char **strp)
740 const char *str = *strp;
741 const char *start;
742 char *ret, *dst;
744 str++;
745 start = str;
746 while (1) {
747 int c = *str++;
749 if (c == 0)
750 goto error;
751 if (c == '"')
752 break;
753 if (c == '\\') {
754 if (*str++ == 0)
755 goto error;
758 *strp = str;
759 ret = xnew(char, str - start);
760 str = start;
761 dst = ret;
762 while (1) {
763 int c = *str++;
765 if (c == '"')
766 break;
767 if (c == '\\') {
768 c = *str++;
769 if (c != '"' && c != '\\')
770 *dst++ = '\\';
772 *dst++ = c;
774 *dst = 0;
775 return ret;
776 error:
777 error_msg("`\"' expected");
778 return NULL;
781 static char *parse_escaped(const char **strp)
783 const char *str = *strp;
784 const char *start;
785 char *ret, *dst;
787 start = str;
788 while (1) {
789 int c = *str;
791 if (c == 0 || c == ' ' || c == '\'' || c == '"')
792 break;
794 str++;
795 if (c == '\\') {
796 c = *str;
797 if (c == 0)
798 break;
799 str++;
802 *strp = str;
803 ret = xnew(char, str - start + 1);
804 str = start;
805 dst = ret;
806 while (1) {
807 int c = *str;
809 if (c == 0 || c == ' ' || c == '\'' || c == '"')
810 break;
812 str++;
813 if (c == '\\') {
814 c = *str;
815 if (c == 0) {
816 *dst++ = '\\';
817 break;
819 str++;
821 *dst++ = c;
823 *dst = 0;
824 return ret;
827 static char *parse_one(const char **strp)
829 const char *str = *strp;
830 char *ret = NULL;
832 while (1) {
833 char *part;
834 int c = *str;
836 if (!c || c == ' ')
837 break;
838 if (c == '"') {
839 part = parse_quoted(&str);
840 if (part == NULL)
841 goto error;
842 } else if (c == '\'') {
843 /* backslashes are normal chars inside single-quotes */
844 const char *end;
846 str++;
847 end = strchr(str, '\'');
848 if (end == NULL)
849 goto sq_missing;
850 part = xstrndup(str, end - str);
851 str = end + 1;
852 } else {
853 part = parse_escaped(&str);
856 if (ret == NULL) {
857 ret = part;
858 } else {
859 char *tmp = xstrjoin(ret, part);
860 free(ret);
861 ret = tmp;
864 *strp = str;
865 return ret;
866 sq_missing:
867 error_msg("`'' expected");
868 error:
869 free(ret);
870 return NULL;
873 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
875 char **av = NULL;
876 int nr = 0;
877 int alloc = 0;
879 while (*cmd) {
880 char *arg;
882 /* there can't be spaces at start of command
883 * and there is at least one argument */
884 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
885 /* {} is replaced with file arguments */
886 if (*args_idx != -1)
887 goto only_once_please;
888 *args_idx = nr;
889 cmd += 2;
890 goto skip_spaces;
891 } else {
892 arg = parse_one(&cmd);
893 if (arg == NULL)
894 goto error;
897 if (nr == alloc) {
898 alloc = alloc ? alloc * 2 : 4;
899 av = xrenew(char *, av, alloc + 1);
901 av[nr++] = arg;
902 skip_spaces:
903 while (*cmd == ' ')
904 cmd++;
906 av[nr] = NULL;
907 *ac = nr;
908 return av;
909 only_once_please:
910 error_msg("{} can be used only once");
911 error:
912 while (nr > 0)
913 free(av[--nr]);
914 free(av);
915 return NULL;
918 static struct track_info **sel_tis;
919 static int sel_tis_alloc;
920 static int sel_tis_nr;
922 static int add_ti(void *data, struct track_info *ti)
924 if (sel_tis_nr == sel_tis_alloc) {
925 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
926 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
928 track_info_ref(ti);
929 sel_tis[sel_tis_nr++] = ti;
930 return 0;
933 static void cmd_run(char *arg)
935 char **av, **argv;
936 int ac, argc, i, run, files_idx = -1;
938 if (cur_view > QUEUE_VIEW) {
939 info_msg("Command execution is supported only in views 1-4");
940 return;
943 av = parse_cmd(arg, &files_idx, &ac);
944 if (av == NULL) {
945 return;
948 /* collect selected files (struct track_info) */
949 sel_tis = NULL;
950 sel_tis_alloc = 0;
951 sel_tis_nr = 0;
953 editable_lock();
954 switch (cur_view) {
955 case TREE_VIEW:
956 __tree_for_each_sel(add_ti, NULL, 0);
957 break;
958 case SORTED_VIEW:
959 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
960 break;
961 case PLAYLIST_VIEW:
962 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
963 break;
964 case QUEUE_VIEW:
965 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
966 break;
968 editable_unlock();
970 if (sel_tis_nr == 0) {
971 /* no files selected, do nothing */
972 free_str_array(av);
973 return;
975 sel_tis[sel_tis_nr] = NULL;
977 /* build argv */
978 argv = xnew(char *, ac + sel_tis_nr + 1);
979 argc = 0;
980 if (files_idx == -1) {
981 /* add selected files after rest of the args */
982 for (i = 0; i < ac; i++)
983 argv[argc++] = av[i];
984 for (i = 0; i < sel_tis_nr; i++)
985 argv[argc++] = sel_tis[i]->filename;
986 } else {
987 for (i = 0; i < files_idx; i++)
988 argv[argc++] = av[i];
989 for (i = 0; i < sel_tis_nr; i++)
990 argv[argc++] = sel_tis[i]->filename;
991 for (i = files_idx; i < ac; i++)
992 argv[argc++] = av[i];
994 argv[argc] = NULL;
996 for (i = 0; argv[i]; i++)
997 d_print("ARG: '%s'\n", argv[i]);
999 run = 1;
1000 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
1001 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
1002 info_msg("Aborted");
1003 run = 0;
1006 if (run) {
1007 int status;
1009 if (spawn(argv, &status)) {
1010 error_msg("executing %s: %s", argv[0], strerror(errno));
1011 } else {
1012 if (WIFEXITED(status)) {
1013 int rc = WEXITSTATUS(status);
1015 if (rc)
1016 error_msg("%s returned %d", argv[0], rc);
1018 if (WIFSIGNALED(status))
1019 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1021 switch (cur_view) {
1022 case TREE_VIEW:
1023 case SORTED_VIEW:
1024 /* this must be done before sel_tis are unreffed */
1025 free_str_array(av);
1026 free(argv);
1028 /* remove non-existed files, update tags for changed files */
1029 cmus_update_tis(sel_tis, sel_tis_nr);
1031 /* we don't own sel_tis anymore! */
1032 return;
1036 free_str_array(av);
1037 free(argv);
1038 for (i = 0; sel_tis[i]; i++)
1039 track_info_unref(sel_tis[i]);
1040 free(sel_tis);
1043 static int get_one_ti(void *data, struct track_info *ti)
1045 struct track_info **sel_ti = data;
1047 track_info_ref(ti);
1048 *sel_ti = ti;
1049 /* stop the for each loop, we need only the first selected track */
1050 return 1;
1053 static void cmd_echo(char *arg)
1055 struct track_info *sel_ti;
1056 char *ptr = arg;
1058 while (1) {
1059 ptr = strchr(ptr, '{');
1060 if (ptr == NULL)
1061 break;
1062 if (ptr[1] == '}')
1063 break;
1064 ptr++;
1067 if (ptr == NULL) {
1068 info_msg("%s", arg);
1069 return;
1072 if (cur_view > QUEUE_VIEW) {
1073 info_msg("echo with {} in its arguments is supported only in views 1-4");
1074 return;
1077 *ptr = 0;
1078 ptr += 2;
1080 /* get only the first selected track */
1081 sel_ti = NULL;
1083 editable_lock();
1084 switch (cur_view) {
1085 case TREE_VIEW:
1086 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1087 break;
1088 case SORTED_VIEW:
1089 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1090 break;
1091 case PLAYLIST_VIEW:
1092 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1093 break;
1094 case QUEUE_VIEW:
1095 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1096 break;
1098 editable_unlock();
1100 if (sel_ti == NULL)
1101 return;
1103 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1104 track_info_unref(sel_ti);
1107 #define VF_RELATIVE 0x01
1108 #define VF_PERCENTAGE 0x02
1110 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1112 unsigned int f = 0;
1113 int ch, val = 0, digits = 0, sign = 1;
1115 if (*arg == '-') {
1116 arg++;
1117 f |= VF_RELATIVE;
1118 sign = -1;
1119 } else if (*arg == '+') {
1120 arg++;
1121 f |= VF_RELATIVE;
1124 while (1) {
1125 ch = *arg++;
1126 if (ch < '0' || ch > '9')
1127 break;
1128 val *= 10;
1129 val += ch - '0';
1130 digits++;
1132 if (digits == 0)
1133 goto err;
1135 if (ch == '%') {
1136 f |= VF_PERCENTAGE;
1137 ch = *arg;
1139 if (ch)
1140 goto err;
1142 *value = sign * val;
1143 *flags = f;
1144 return 0;
1145 err:
1146 return -1;
1149 static int calc_vol(int val, int old, int max, unsigned int flags)
1151 if (flags & VF_RELATIVE) {
1152 if (flags & VF_PERCENTAGE)
1153 val = scale_from_percentage(val, max);
1154 val += old;
1155 } else if (flags & VF_PERCENTAGE) {
1156 val = scale_from_percentage(val, max);
1158 return clamp(val, 0, max);
1162 * :vol value [value]
1164 * where value is [-+]?[0-9]+%?
1166 static void cmd_vol(char *arg)
1168 char **values = get_words(arg);
1169 unsigned int lf, rf;
1170 int l, r;
1172 if (values[1] && values[2])
1173 goto err;
1175 if (parse_vol_arg(values[0], &l, &lf))
1176 goto err;
1178 r = l;
1179 rf = lf;
1180 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1181 goto err;
1183 free_str_array(values);
1185 if (soft_vol) {
1186 l = calc_vol(l, soft_vol_l, 100, lf);
1187 r = calc_vol(r, soft_vol_r, 100, rf);
1188 player_set_soft_volume(l, r);
1189 } else {
1190 mixer_read_volume();
1191 l = calc_vol(l, volume_l, volume_max, lf);
1192 r = calc_vol(r, volume_r, volume_max, rf);
1193 mixer_set_volume(l, r);
1195 update_statusline();
1196 return;
1197 err:
1198 free_str_array(values);
1199 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1202 static void cmd_prev_view(char *arg)
1204 int tmp;
1205 if (prev_view >= 0) {
1206 tmp = cur_view;
1207 set_view(prev_view);
1208 prev_view = tmp;
1212 static void cmd_view(char *arg)
1214 int view;
1216 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view) && (view - 1) != cur_view) {
1217 prev_view = cur_view;
1218 set_view(view - 1);
1222 static void cmd_p_next(char *arg)
1224 cmus_next();
1227 static void cmd_p_pause(char *arg)
1229 player_pause();
1232 static void cmd_p_play(char *arg)
1234 if (arg) {
1235 cmus_play_file(arg);
1236 } else {
1237 player_play();
1241 static void cmd_p_prev(char *arg)
1243 cmus_prev();
1246 static void cmd_p_stop(char *arg)
1248 player_stop();
1251 static void cmd_search_next(char *arg)
1253 if (search_str) {
1254 if (!search_next(searchable, search_str, search_direction))
1255 search_not_found();
1259 static void cmd_search_prev(char *arg)
1261 if (search_str) {
1262 if (!search_next(searchable, search_str, !search_direction))
1263 search_not_found();
1267 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1269 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1272 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1274 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1277 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1279 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1282 static for_each_sel_ti_cb view_for_each_sel[4] = {
1283 tree_for_each_sel,
1284 sorted_for_each_sel,
1285 pl_for_each_sel,
1286 pq_for_each_sel
1289 /* wrapper for void lib_add_track(struct track_info *) etc. */
1290 static int wrapper_cb(void *data, struct track_info *ti)
1292 add_ti_cb add = data;
1294 add(ti);
1295 return 0;
1298 static void add_from_browser(add_ti_cb add, int job_type)
1300 char *sel = browser_get_sel();
1302 if (sel) {
1303 enum file_type ft;
1304 char *ret;
1306 ft = cmus_detect_ft(sel, &ret);
1307 if (ft != FILE_TYPE_INVALID) {
1308 cmus_add(add, ret, ft, job_type);
1309 window_down(browser_win, 1);
1311 free(ret);
1312 free(sel);
1316 static void cmd_win_add_l(char *arg)
1318 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1319 return;
1321 if (cur_view <= QUEUE_VIEW) {
1322 editable_lock();
1323 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1324 editable_unlock();
1325 } else if (cur_view == BROWSER_VIEW) {
1326 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1330 static void cmd_win_add_p(char *arg)
1332 /* could allow adding dups? */
1333 if (cur_view == PLAYLIST_VIEW)
1334 return;
1336 if (cur_view <= QUEUE_VIEW) {
1337 editable_lock();
1338 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1339 editable_unlock();
1340 } else if (cur_view == BROWSER_VIEW) {
1341 add_from_browser(pl_add_track, JOB_TYPE_PL);
1345 static void cmd_win_add_Q(char *arg)
1347 if (cur_view == QUEUE_VIEW)
1348 return;
1350 if (cur_view <= QUEUE_VIEW) {
1351 editable_lock();
1352 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1353 editable_unlock();
1354 } else if (cur_view == BROWSER_VIEW) {
1355 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1359 static void cmd_win_add_q(char *arg)
1361 if (cur_view == QUEUE_VIEW)
1362 return;
1364 if (cur_view <= QUEUE_VIEW) {
1365 editable_lock();
1366 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1367 editable_unlock();
1368 } else if (cur_view == BROWSER_VIEW) {
1369 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1373 static void cmd_win_activate(char *arg)
1375 struct track_info *info = NULL;
1377 editable_lock();
1378 switch (cur_view) {
1379 case TREE_VIEW:
1380 info = tree_set_selected();
1381 break;
1382 case SORTED_VIEW:
1383 info = sorted_set_selected();
1384 break;
1385 case PLAYLIST_VIEW:
1386 info = pl_set_selected();
1387 break;
1388 case QUEUE_VIEW:
1389 break;
1390 case BROWSER_VIEW:
1391 browser_enter();
1392 break;
1393 case FILTERS_VIEW:
1394 filters_activate();
1395 break;
1396 case HELP_VIEW:
1397 help_select();
1398 break;
1400 editable_unlock();
1402 if (info) {
1403 /* update lib/pl mode */
1404 if (cur_view < 2)
1405 play_library = 1;
1406 if (cur_view == 2)
1407 play_library = 0;
1409 player_play_file(info);
1413 static void cmd_win_mv_after(char *arg)
1415 editable_lock();
1416 switch (cur_view) {
1417 case TREE_VIEW:
1418 break;
1419 case SORTED_VIEW:
1420 editable_move_after(&lib_editable);
1421 break;
1422 case PLAYLIST_VIEW:
1423 editable_move_after(&pl_editable);
1424 break;
1425 case QUEUE_VIEW:
1426 editable_move_after(&pq_editable);
1427 break;
1428 case BROWSER_VIEW:
1429 break;
1430 case FILTERS_VIEW:
1431 break;
1432 case HELP_VIEW:
1433 break;
1435 editable_unlock();
1438 static void cmd_win_mv_before(char *arg)
1440 editable_lock();
1441 switch (cur_view) {
1442 case TREE_VIEW:
1443 break;
1444 case SORTED_VIEW:
1445 editable_move_before(&lib_editable);
1446 break;
1447 case PLAYLIST_VIEW:
1448 editable_move_before(&pl_editable);
1449 break;
1450 case QUEUE_VIEW:
1451 editable_move_before(&pq_editable);
1452 break;
1453 case BROWSER_VIEW:
1454 break;
1455 case FILTERS_VIEW:
1456 break;
1457 case HELP_VIEW:
1458 break;
1460 editable_unlock();
1463 static void cmd_win_remove(char *arg)
1465 editable_lock();
1466 switch (cur_view) {
1467 case TREE_VIEW:
1468 tree_remove_sel();
1469 break;
1470 case SORTED_VIEW:
1471 editable_remove_sel(&lib_editable);
1472 break;
1473 case PLAYLIST_VIEW:
1474 editable_remove_sel(&pl_editable);
1475 break;
1476 case QUEUE_VIEW:
1477 editable_remove_sel(&pq_editable);
1478 break;
1479 case BROWSER_VIEW:
1480 browser_delete();
1481 break;
1482 case FILTERS_VIEW:
1483 filters_delete_filter();
1484 break;
1485 case HELP_VIEW:
1486 help_remove();
1487 break;
1489 editable_unlock();
1492 static void cmd_win_sel_cur(char *arg)
1494 editable_lock();
1495 switch (cur_view) {
1496 case TREE_VIEW:
1497 tree_sel_current();
1498 break;
1499 case SORTED_VIEW:
1500 sorted_sel_current();
1501 break;
1502 case PLAYLIST_VIEW:
1503 pl_sel_current();
1504 break;
1505 case QUEUE_VIEW:
1506 break;
1507 case BROWSER_VIEW:
1508 break;
1509 case FILTERS_VIEW:
1510 break;
1511 case HELP_VIEW:
1512 break;
1514 editable_unlock();
1517 static void cmd_win_toggle(char *arg)
1519 switch (cur_view) {
1520 case TREE_VIEW:
1521 editable_lock();
1522 tree_toggle_expand_artist();
1523 editable_unlock();
1524 break;
1525 case SORTED_VIEW:
1526 editable_lock();
1527 editable_toggle_mark(&lib_editable);
1528 editable_unlock();
1529 break;
1530 case PLAYLIST_VIEW:
1531 editable_lock();
1532 editable_toggle_mark(&pl_editable);
1533 editable_unlock();
1534 break;
1535 case QUEUE_VIEW:
1536 editable_lock();
1537 editable_toggle_mark(&pq_editable);
1538 editable_unlock();
1539 break;
1540 case BROWSER_VIEW:
1541 break;
1542 case FILTERS_VIEW:
1543 filters_toggle_filter();
1544 break;
1545 case HELP_VIEW:
1546 help_toggle();
1547 break;
1551 static struct window *current_win(void)
1553 switch (cur_view) {
1554 case TREE_VIEW:
1555 return lib_cur_win;
1556 case SORTED_VIEW:
1557 return lib_editable.win;
1558 case PLAYLIST_VIEW:
1559 return pl_editable.win;
1560 case QUEUE_VIEW:
1561 return pq_editable.win;
1562 case BROWSER_VIEW:
1563 return browser_win;
1564 case HELP_VIEW:
1565 return help_win;
1566 case FILTERS_VIEW:
1567 default:
1568 return filters_win;
1572 static void cmd_win_bottom(char *arg)
1574 editable_lock();
1575 window_goto_bottom(current_win());
1576 editable_unlock();
1579 static void cmd_win_down(char *arg)
1581 editable_lock();
1582 window_down(current_win(), 1);
1583 editable_unlock();
1586 static void cmd_win_next(char *arg)
1588 if (cur_view == TREE_VIEW) {
1589 editable_lock();
1590 tree_toggle_active_window();
1591 editable_unlock();
1595 static void cmd_win_pg_down(char *arg)
1597 editable_lock();
1598 window_page_down(current_win());
1599 editable_unlock();
1602 static void cmd_win_pg_up(char *arg)
1604 editable_lock();
1605 window_page_up(current_win());
1606 editable_unlock();
1609 static void cmd_win_top(char *arg)
1611 editable_lock();
1612 window_goto_top(current_win());
1613 editable_unlock();
1616 static void cmd_win_up(char *arg)
1618 editable_lock();
1619 window_up(current_win(), 1);
1620 editable_unlock();
1623 static void cmd_win_update(char *arg)
1625 switch (cur_view) {
1626 case TREE_VIEW:
1627 case SORTED_VIEW:
1628 cmus_update_lib();
1629 break;
1630 case BROWSER_VIEW:
1631 browser_reload();
1632 break;
1636 static void cmd_browser_up(char *arg)
1638 browser_up();
1641 static void cmd_refresh(char *arg)
1643 clearok(curscr, TRUE);
1644 refresh();
1647 static int cmp_intp(const void *ap, const void *bp)
1649 int a = *(int *)ap;
1650 int b = *(int *)bp;
1651 return a - b;
1654 static int *rand_array(int size, int nmax)
1656 int *r = xnew(int, size + 1);
1657 int i, offset = 0;
1658 int count = size;
1660 if (count > nmax / 2) {
1662 * Imagine that there are 1000 tracks in library and we want to
1663 * add 998 random tracks to queue. After we have added 997
1664 * random numbers to the array it would be quite hard to find a
1665 * random number that isn't already in the array (3/1000
1666 * probability).
1668 * So we invert the logic:
1670 * Find two (1000 - 998) random numbers in 0..999 range and put
1671 * them at end of the array. Sort the numbers and then fill
1672 * the array starting at index 0 with incrementing values that
1673 * are not in the set of random numbers.
1675 count = nmax - count;
1676 offset = size - count;
1679 for (i = 0; i < count; ) {
1680 int v, j;
1681 found:
1682 v = rand() % nmax;
1683 for (j = 0; j < i; j++) {
1684 if (r[offset + j] == v)
1685 goto found;
1687 r[offset + i++] = v;
1689 qsort(r + offset, count, sizeof(*r), cmp_intp);
1691 if (offset) {
1692 int j, n;
1694 /* simplifies next loop */
1695 r[size] = nmax;
1697 /* convert the indexes we don't want to those we want */
1698 i = 0;
1699 j = offset;
1700 n = 0;
1701 do {
1702 while (n < r[j])
1703 r[i++] = n++;
1704 j++;
1705 n++;
1706 } while (i < size);
1708 return r;
1711 static int count_albums(void)
1713 struct artist *artist;
1714 struct list_head *item;
1715 int count = 0;
1717 list_for_each_entry(artist, &lib_artist_head, node) {
1718 list_for_each(item, &artist->album_head)
1719 count++;
1721 return count;
1724 struct album_list {
1725 struct list_head node;
1726 const struct album *album;
1729 static void cmd_lqueue(char *arg)
1731 LIST_HEAD(head);
1732 const struct list_head *item;
1733 const struct album *album;
1734 int count = 1, nmax, i, pos;
1735 int *r;
1737 if (arg) {
1738 long int val;
1740 if (str_to_int(arg, &val) || val <= 0) {
1741 error_msg("argument must be positive integer");
1742 return;
1744 count = val;
1746 editable_lock();
1747 nmax = count_albums();
1748 if (count > nmax)
1749 count = nmax;
1750 if (!count)
1751 goto unlock;
1753 r = rand_array(count, nmax);
1754 album = to_album(to_artist(lib_artist_head.next)->album_head.next);
1755 pos = 0;
1756 for (i = 0; i < count; i++) {
1757 struct album_list *a;
1759 while (pos < r[i]) {
1760 struct artist *artist = album->artist;
1761 if (album->node.next == &artist->album_head) {
1762 artist = to_artist(artist->node.next);
1763 album = to_album(artist->album_head.next);
1764 } else {
1765 album = to_album(album->node.next);
1767 pos++;
1769 a = xnew(struct album_list, 1);
1770 a->album = album;
1771 list_add_rand(&head, &a->node, i);
1773 free(r);
1775 item = head.next;
1776 do {
1777 struct list_head *next = item->next;
1778 struct album_list *a = container_of(item, struct album_list, node);
1779 struct tree_track *t;
1781 list_for_each_entry(t, &a->album->track_head, node)
1782 editable_add(&pq_editable, simple_track_new(tree_track_info(t)));
1783 free(a);
1784 item = next;
1785 } while (item != &head);
1786 unlock:
1787 editable_unlock();
1790 static void cmd_tqueue(char *arg)
1792 LIST_HEAD(head);
1793 struct list_head *item;
1794 int count = 1, i, pos;
1795 int *r;
1797 if (arg) {
1798 long int val;
1800 if (str_to_int(arg, &val) || val <= 0) {
1801 error_msg("argument must be positive integer");
1802 return;
1804 count = val;
1806 editable_lock();
1807 if (count > lib_editable.nr_tracks)
1808 count = lib_editable.nr_tracks;
1809 if (!count)
1810 goto unlock;
1812 r = rand_array(count, lib_editable.nr_tracks);
1813 item = lib_editable.head.next;
1814 pos = 0;
1815 for (i = 0; i < count; i++) {
1816 struct simple_track *t;
1818 while (pos < r[i]) {
1819 item = item->next;
1820 pos++;
1822 t = simple_track_new(to_simple_track(item)->info);
1823 list_add_rand(&head, &t->node, i);
1825 free(r);
1827 item = head.next;
1828 do {
1829 struct list_head *next = item->next;
1830 struct simple_track *t = to_simple_track(item);
1831 editable_add(&pq_editable, t);
1832 item = next;
1833 } while (item != &head);
1834 unlock:
1835 editable_unlock();
1838 /* tab exp {{{
1840 * these functions fill tabexp struct, which is resetted beforehand
1843 /* buffer used for tab expansion */
1844 static char expbuf[512];
1846 static int filter_directories(const char *name, const struct stat *s)
1848 return S_ISDIR(s->st_mode);
1851 static int filter_any(const char *name, const struct stat *s)
1853 return 1;
1856 static int filter_playable(const char *name, const struct stat *s)
1858 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1861 static int filter_playlist(const char *name, const struct stat *s)
1863 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1866 static int filter_supported(const char *name, const struct stat *s)
1868 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1871 static void expand_files(const char *str)
1873 expand_files_and_dirs(str, filter_any);
1876 static void expand_directories(const char *str)
1878 expand_files_and_dirs(str, filter_directories);
1881 static void expand_playable(const char *str)
1883 expand_files_and_dirs(str, filter_playable);
1886 static void expand_playlist(const char *str)
1888 expand_files_and_dirs(str, filter_playlist);
1891 static void expand_supported(const char *str)
1893 expand_files_and_dirs(str, filter_supported);
1896 static void expand_add(const char *str)
1898 int flag = parse_flags(&str, "lpqQ");
1900 if (flag == -1)
1901 return;
1902 if (str == NULL)
1903 str = "";
1904 expand_supported(str);
1906 if (tabexp.head && flag) {
1907 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1908 free(tabexp.head);
1909 tabexp.head = xstrdup(expbuf);
1913 static void expand_load_save(const char *str)
1915 int flag = parse_flags(&str, "lp");
1917 if (flag == -1)
1918 return;
1919 if (str == NULL)
1920 str = "";
1921 expand_playlist(str);
1923 if (tabexp.head && flag) {
1924 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1925 free(tabexp.head);
1926 tabexp.head = xstrdup(expbuf);
1930 static void expand_key_context(const char *str, const char *force)
1932 int pos, i, len = strlen(str);
1933 char **tails;
1935 tails = xnew(char *, NR_CTXS + 1);
1936 pos = 0;
1937 for (i = 0; key_context_names[i]; i++) {
1938 int cmp = strncmp(str, key_context_names[i], len);
1939 if (cmp > 0)
1940 continue;
1941 if (cmp < 0)
1942 break;
1943 tails[pos++] = xstrdup(key_context_names[i] + len);
1946 if (pos == 0) {
1947 free(tails);
1948 return;
1950 if (pos == 1) {
1951 char *tmp = xstrjoin(tails[0], " ");
1952 free(tails[0]);
1953 tails[0] = tmp;
1955 tails[pos] = NULL;
1956 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1957 tabexp.head = xstrdup(expbuf);
1958 tabexp.tails = tails;
1961 static int get_context(const char *str, int len)
1963 int i, c = -1, count = 0;
1965 for (i = 0; key_context_names[i]; i++) {
1966 if (strncmp(str, key_context_names[i], len) == 0) {
1967 if (key_context_names[i][len] == 0) {
1968 /* exact */
1969 return i;
1971 c = i;
1972 count++;
1975 if (count == 1)
1976 return c;
1977 return -1;
1980 static void expand_command_line(const char *str);
1982 static void expand_bind_args(const char *str)
1984 /* :bind context key function
1986 * possible values for str:
1988 * context k
1989 * context key f
1991 * you need to know context before you can expand function
1993 /* start and end pointers for context, key and function */
1994 const char *cs, *ce, *ks, *ke, *fs;
1995 int i, c, k, count;
1996 int flag = parse_flags((const char **)&str, "f");
1997 const char *force = "";
1999 if (flag == -1)
2000 return;
2001 if (str == NULL)
2002 str = "";
2004 if (flag == 'f')
2005 force = "-f ";
2007 cs = str;
2008 ce = strchr(cs, ' ');
2009 if (ce == NULL) {
2010 expand_key_context(cs, force);
2011 return;
2014 /* context must be expandable */
2015 c = get_context(cs, ce - cs);
2016 if (c == -1) {
2017 /* context is ambiguous or invalid */
2018 return;
2021 ks = ce;
2022 while (*ks == ' ')
2023 ks++;
2024 ke = strchr(ks, ' ');
2025 if (ke == NULL) {
2026 /* expand key */
2027 int len = strlen(ks);
2028 PTR_ARRAY(array);
2030 for (i = 0; key_table[i].name; i++) {
2031 int cmp = strncmp(ks, key_table[i].name, len);
2032 if (cmp > 0)
2033 continue;
2034 if (cmp < 0)
2035 break;
2036 ptr_array_add(&array, xstrdup(key_table[i].name + len));
2039 if (!array.count)
2040 return;
2042 if (array.count == 1) {
2043 char **ptrs = array.ptrs;
2044 char *tmp = xstrjoin(ptrs[0], " ");
2045 free(ptrs[0]);
2046 ptrs[0] = tmp;
2049 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
2051 ptr_array_plug(&array);
2052 tabexp.head = xstrdup(expbuf);
2053 tabexp.tails = array.ptrs;
2054 return;
2057 /* key must be expandable */
2058 k = -1;
2059 count = 0;
2060 for (i = 0; key_table[i].name; i++) {
2061 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
2062 if (key_table[i].name[ke - ks] == 0) {
2063 /* exact */
2064 k = i;
2065 count = 1;
2066 break;
2068 k = i;
2069 count++;
2072 if (count != 1) {
2073 /* key is ambiguous or invalid */
2074 return;
2077 fs = ke;
2078 while (*fs == ' ')
2079 fs++;
2081 if (*fs == ':')
2082 fs++;
2084 /* expand com [arg...] */
2085 expand_command_line(fs);
2086 if (tabexp.head == NULL) {
2087 /* command expand failed */
2088 return;
2092 * tabexp.head is now "com"
2093 * tabexp.tails is [ mand1 mand2 ... ]
2095 * need to change tabexp.head to "context key com"
2098 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
2099 key_table[k].name, tabexp.head);
2100 free(tabexp.head);
2101 tabexp.head = xstrdup(expbuf);
2104 static void expand_unbind_args(const char *str)
2106 /* :unbind context key */
2107 /* start and end pointers for context and key */
2108 const char *cs, *ce, *ks;
2109 const struct binding *b;
2110 PTR_ARRAY(array);
2111 int c, len;
2113 cs = str;
2114 ce = strchr(cs, ' ');
2115 if (ce == NULL) {
2116 expand_key_context(cs, "");
2117 return;
2120 /* context must be expandable */
2121 c = get_context(cs, ce - cs);
2122 if (c == -1) {
2123 /* context is ambiguous or invalid */
2124 return;
2127 ks = ce;
2128 while (*ks == ' ')
2129 ks++;
2131 /* expand key */
2132 len = strlen(ks);
2133 b = key_bindings[c];
2134 while (b) {
2135 if (!strncmp(ks, b->key->name, len))
2136 ptr_array_add(&array, xstrdup(b->key->name + len));
2137 b = b->next;
2139 if (!array.count)
2140 return;
2142 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
2144 ptr_array_plug(&array);
2145 tabexp.head = xstrdup(expbuf);
2146 tabexp.tails = array.ptrs;
2149 static void expand_factivate(const char *str)
2151 /* "name1 name2 name3", expand only name3 */
2152 struct filter_entry *e;
2153 const char *name;
2154 PTR_ARRAY(array);
2155 int str_len, len, i;
2157 str_len = strlen(str);
2158 i = str_len;
2159 while (i > 0) {
2160 if (str[i - 1] == ' ')
2161 break;
2162 i--;
2164 len = str_len - i;
2165 name = str + i;
2167 list_for_each_entry(e, &filters_head, node) {
2168 if (!strncmp(name, e->name, len))
2169 ptr_array_add(&array, xstrdup(e->name + len));
2171 if (!array.count)
2172 return;
2174 ptr_array_plug(&array);
2175 tabexp.head = xstrdup(str);
2176 tabexp.tails = array.ptrs;
2179 static void expand_options(const char *str)
2181 struct cmus_opt *opt;
2182 int len;
2183 char **tails;
2185 /* tabexp is resetted */
2186 len = strlen(str);
2187 if (len > 1 && str[len - 1] == '=') {
2188 /* expand value */
2189 char *var = xstrndup(str, len - 1);
2191 list_for_each_entry(opt, &option_head, node) {
2192 if (strcmp(var, opt->name) == 0) {
2193 char buf[OPTION_MAX_SIZE];
2195 tails = xnew(char *, 2);
2197 buf[0] = 0;
2198 opt->get(opt->id, buf);
2199 tails[0] = xstrdup(buf);
2200 tails[1] = NULL;
2202 tabexp.head = xstrdup(str);
2203 tabexp.tails = tails;
2204 free(var);
2205 return;
2208 free(var);
2209 } else {
2210 /* expand variable */
2211 int pos;
2213 tails = xnew(char *, nr_options + 1);
2214 pos = 0;
2215 list_for_each_entry(opt, &option_head, node) {
2216 if (strncmp(str, opt->name, len) == 0)
2217 tails[pos++] = xstrdup(opt->name + len);
2219 if (pos > 0) {
2220 if (pos == 1) {
2221 /* only one variable matches, add '=' */
2222 char *tmp = xstrjoin(tails[0], "=");
2224 free(tails[0]);
2225 tails[0] = tmp;
2228 tails[pos] = NULL;
2229 tabexp.head = xstrdup(str);
2230 tabexp.tails = tails;
2231 } else {
2232 free(tails);
2237 static void expand_toptions(const char *str)
2239 struct cmus_opt *opt;
2240 int len, pos;
2241 char **tails;
2243 tails = xnew(char *, nr_options + 1);
2244 len = strlen(str);
2245 pos = 0;
2246 list_for_each_entry(opt, &option_head, node) {
2247 if (opt->toggle == NULL)
2248 continue;
2249 if (strncmp(str, opt->name, len) == 0)
2250 tails[pos++] = xstrdup(opt->name + len);
2252 if (pos > 0) {
2253 tails[pos] = NULL;
2254 tabexp.head = xstrdup(str);
2255 tabexp.tails = tails;
2256 } else {
2257 free(tails);
2261 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2263 struct directory dir;
2264 const char *name, *dot;
2265 int len = strlen(str);
2267 if (dir_open(&dir, dirname))
2268 return;
2270 while ((name = dir_read(&dir))) {
2271 if (!S_ISREG(dir.st.st_mode))
2272 continue;
2273 if (strncmp(name, str, len))
2274 continue;
2275 dot = strrchr(name, '.');
2276 if (dot == NULL || strcmp(dot, ".theme"))
2277 continue;
2278 if (dot - name < len)
2279 /* str is "foo.th"
2280 * matches "foo.theme"
2281 * which also ends with ".theme"
2283 continue;
2284 ptr_array_add(array, xstrndup(name + len, dot - name - len));
2286 dir_close(&dir);
2289 static void expand_colorscheme(const char *str)
2291 PTR_ARRAY(array);
2293 load_themes(cmus_config_dir, str, &array);
2294 load_themes(DATADIR "/cmus", str, &array);
2296 if (array.count) {
2297 ptr_array_sort(&array, strptrcmp);
2299 ptr_array_plug(&array);
2300 tabexp.head = xstrdup(str);
2301 tabexp.tails = array.ptrs;
2305 /* tab exp }}} */
2307 /* sort by name */
2308 struct command commands[] = {
2309 { "add", cmd_add, 1, 1, expand_add, 0, 0 },
2310 { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
2311 { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
2312 { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
2313 { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
2314 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
2315 { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
2316 { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
2317 { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
2318 { "fset", cmd_fset, 1, 1, NULL, 0, 0 },
2319 { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
2320 { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
2321 { "lqueue", cmd_lqueue, 0, 1, NULL, 0, 0 },
2322 { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
2323 { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
2324 { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
2325 { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
2326 { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
2327 { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
2328 { "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
2329 { "quit", cmd_quit, 0, 0, NULL, 0, 0 },
2330 { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
2331 { "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
2332 { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
2333 { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
2334 { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
2335 { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
2336 { "set", cmd_set, 1, 1, expand_options, 0, 0 },
2337 { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
2338 { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
2339 { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
2340 { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
2341 { "tqueue", cmd_tqueue, 0, 1, NULL, 0, 0 },
2342 { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
2343 { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
2344 { "view", cmd_view, 1, 1, NULL, 0, 0 },
2345 { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
2346 { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
2347 { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
2348 { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
2349 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
2350 { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
2351 { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
2352 { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
2353 { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
2354 { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
2355 { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
2356 { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
2357 { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
2358 { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
2359 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
2360 { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
2361 { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
2362 { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
2363 { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
2364 { NULL, NULL, 0, 0, 0, 0, 0 }
2367 /* fills tabexp struct */
2368 static void expand_commands(const char *str)
2370 int i, len, pos;
2371 char **tails;
2373 /* tabexp is resetted */
2374 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2375 len = strlen(str);
2376 pos = 0;
2377 for (i = 0; commands[i].name; i++) {
2378 if (strncmp(str, commands[i].name, len) == 0)
2379 tails[pos++] = xstrdup(commands[i].name + len);
2381 if (pos > 0) {
2382 if (pos == 1) {
2383 /* only one command matches, add ' ' */
2384 char *tmp = xstrjoin(tails[0], " ");
2386 free(tails[0]);
2387 tails[0] = tmp;
2389 tails[pos] = NULL;
2390 tabexp.head = xstrdup(str);
2391 tabexp.tails = tails;
2392 } else {
2393 free(tails);
2397 struct command *get_command(const char *str)
2399 int i, len;
2401 while (*str == ' ')
2402 str++;
2403 for (len = 0; str[len] && str[len] != ' '; len++)
2406 for (i = 0; commands[i].name; i++) {
2407 if (strncmp(str, commands[i].name, len))
2408 continue;
2410 if (commands[i].name[len] == 0) {
2411 /* exact */
2412 return &commands[i];
2415 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2416 /* ambiguous */
2417 return NULL;
2419 return &commands[i];
2421 return NULL;
2424 /* fills tabexp struct */
2425 static void expand_command_line(const char *str)
2427 /* :command [arg]...
2429 * examples:
2431 * str expanded value (tabexp.head)
2432 * -------------------------------------
2433 * fs fset
2434 * b c bind common
2435 * se se (tabexp.tails = [ ek t ])
2437 /* command start/end, argument start */
2438 const char *cs, *ce, *as;
2439 const struct command *cmd;
2441 cs = str;
2442 ce = strchr(cs, ' ');
2443 if (ce == NULL) {
2444 /* expand command */
2445 expand_commands(cs);
2446 return;
2449 /* command must be expandable */
2450 cmd = get_command(cs);
2451 if (cmd == NULL) {
2452 /* command ambiguous or invalid */
2453 return;
2456 if (cmd->expand == NULL) {
2457 /* can't expand argument */
2458 return;
2461 as = ce;
2462 while (*as == ' ')
2463 as++;
2465 /* expand argument */
2466 cmd->expand(as);
2467 if (tabexp.head == NULL) {
2468 /* argument expansion failed */
2469 return;
2472 /* tabexp.head is now start of the argument string */
2473 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2474 free(tabexp.head);
2475 tabexp.head = xstrdup(expbuf);
2478 static void tab_expand(void)
2480 char *s1, *s2, *tmp;
2481 int pos;
2483 /* strip white space */
2484 pos = 0;
2485 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2486 pos++;
2488 /* string to expand */
2489 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2491 /* tail */
2492 s2 = xstrdup(cmdline.line + cmdline.bpos);
2494 tmp = tabexp_expand(s1, expand_command_line);
2495 if (tmp) {
2496 /* tmp.s2 */
2497 int l1, l2;
2499 l1 = strlen(tmp);
2500 l2 = strlen(s2);
2501 cmdline.blen = l1 + l2;
2502 if (cmdline.blen >= cmdline.size) {
2503 while (cmdline.blen >= cmdline.size)
2504 cmdline.size *= 2;
2505 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2507 sprintf(cmdline.line, "%s%s", tmp, s2);
2508 cmdline.bpos = l1;
2509 cmdline.cpos = u_strlen(tmp);
2510 cmdline.clen = u_strlen(cmdline.line);
2511 free(tmp);
2513 free(s1);
2514 free(s2);
2517 static void reset_tab_expansion(void)
2519 tabexp_reset();
2520 arg_expand_cmd = -1;
2523 int run_only_safe_commands;
2525 /* FIXME: parse all arguments */
2526 void run_command(const char *buf)
2528 char *cmd, *arg;
2529 int cmd_start, cmd_end, cmd_len;
2530 int arg_start, arg_end;
2531 int i;
2533 i = 0;
2534 while (buf[i] && buf[i] == ' ')
2535 i++;
2537 if (buf[i] == '#')
2538 return;
2540 cmd_start = i;
2541 while (buf[i] && buf[i] != ' ')
2542 i++;
2543 cmd_end = i;
2544 while (buf[i] && buf[i] == ' ')
2545 i++;
2546 arg_start = i;
2547 while (buf[i])
2548 i++;
2549 arg_end = i;
2551 cmd_len = cmd_end - cmd_start;
2552 if (cmd_len == 0)
2553 return;
2555 cmd = xstrndup(buf + cmd_start, cmd_len);
2556 if (arg_start == arg_end) {
2557 arg = NULL;
2558 } else {
2559 arg = xstrndup(buf + arg_start, arg_end - arg_start);
2561 i = 0;
2562 while (1) {
2563 const struct command *c = &commands[i];
2565 if (c->name == NULL) {
2566 error_msg("unknown command\n");
2567 break;
2569 if (strncmp(cmd, c->name, cmd_len) == 0) {
2570 const char *next = commands[i + 1].name;
2571 int exact = c->name[cmd_len] == 0;
2573 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2574 error_msg("ambiguous command\n");
2575 break;
2577 if (c->min_args > 0 && arg == NULL) {
2578 error_msg("not enough arguments\n");
2579 break;
2581 if (c->max_args == 0 && arg) {
2582 error_msg("too many arguments\n");
2583 break;
2585 if (run_only_safe_commands && c->flags & CMD_UNSAFE) {
2586 d_print("trying to execute unsafe command over net\n");
2587 break;
2589 c->func(arg);
2590 break;
2592 i++;
2594 free(arg);
2595 free(cmd);
2598 static void reset_history_search(void)
2600 history_reset_search(&cmd_history);
2601 free(history_search_text);
2602 history_search_text = NULL;
2605 static void backspace(void)
2607 if (cmdline.clen > 0) {
2608 cmdline_backspace();
2609 } else {
2610 input_mode = NORMAL_MODE;
2614 void command_mode_ch(uchar ch)
2616 switch (ch) {
2617 case 0x01: // ^A
2618 cmdline_move_home();
2619 break;
2620 case 0x02: // ^B
2621 cmdline_move_left();
2622 break;
2623 case 0x04: // ^D
2624 cmdline_delete_ch();
2625 break;
2626 case 0x05: // ^E
2627 cmdline_move_end();
2628 break;
2629 case 0x06: // ^F
2630 cmdline_move_right();
2631 break;
2632 case 0x03: // ^C
2633 case 0x07: // ^G
2634 case 0x1B: // ESC
2635 if (cmdline.blen) {
2636 history_add_line(&cmd_history, cmdline.line);
2637 cmdline_clear();
2639 input_mode = NORMAL_MODE;
2640 break;
2641 case 0x0A:
2642 if (cmdline.blen) {
2643 run_command(cmdline.line);
2644 history_add_line(&cmd_history, cmdline.line);
2645 cmdline_clear();
2647 input_mode = NORMAL_MODE;
2648 break;
2649 case 0x0B:
2650 cmdline_clear_end();
2651 break;
2652 case 0x09:
2653 tab_expand();
2654 break;
2655 case 0x15:
2656 cmdline_backspace_to_bol();
2657 break;
2658 case 127:
2659 backspace();
2660 break;
2661 default:
2662 cmdline_insert_ch(ch);
2664 reset_history_search();
2665 if (ch != 0x09)
2666 reset_tab_expansion();
2669 void command_mode_key(int key)
2671 reset_tab_expansion();
2672 switch (key) {
2673 case KEY_DC:
2674 cmdline_delete_ch();
2675 break;
2676 case KEY_BACKSPACE:
2677 backspace();
2678 break;
2679 case KEY_LEFT:
2680 cmdline_move_left();
2681 return;
2682 case KEY_RIGHT:
2683 cmdline_move_right();
2684 return;
2685 case KEY_HOME:
2686 cmdline_move_home();
2687 return;
2688 case KEY_END:
2689 cmdline_move_end();
2690 return;
2691 case KEY_UP:
2693 const char *s;
2695 if (history_search_text == NULL)
2696 history_search_text = xstrdup(cmdline.line);
2697 s = history_search_forward(&cmd_history, history_search_text);
2698 if (s)
2699 cmdline_set_text(s);
2701 return;
2702 case KEY_DOWN:
2703 if (history_search_text) {
2704 const char *s;
2706 s = history_search_backward(&cmd_history, history_search_text);
2707 if (s) {
2708 cmdline_set_text(s);
2709 } else {
2710 cmdline_set_text(history_search_text);
2713 return;
2714 default:
2715 d_print("key = %c (%d)\n", key, key);
2717 reset_history_search();
2720 void commands_init(void)
2722 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2723 history_load(&cmd_history, cmd_history_filename, 2000);
2726 void commands_exit(void)
2728 history_save(&cmd_history);
2729 free(cmd_history_filename);
2730 tabexp_reset();