Add lqueue command
[cmus.git] / command_mode.c
blob5e1926dcfb54e893856f6bde6b1d57235cde9048
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;
69 static char *get_home_dir(const char *username)
71 struct passwd *passwd;
73 if (username == NULL)
74 return xstrdup(home_dir);
75 passwd = getpwnam(username);
76 if (passwd == NULL)
77 return NULL;
78 /* don't free passwd */
79 return xstrdup(passwd->pw_dir);
82 static char *expand_filename(const char *name)
84 if (name[0] == '~') {
85 char *slash;
87 slash = strchr(name, '/');
88 if (slash) {
89 char *username, *home;
91 if (slash - name - 1 > 0) {
92 /* ~user/... */
93 username = xstrndup(name + 1, slash - name - 1);
94 } else {
95 /* ~/... */
96 username = NULL;
98 home = get_home_dir(username);
99 free(username);
100 if (home) {
101 char *expanded;
103 expanded = xstrjoin(home, slash);
104 free(home);
105 return expanded;
106 } else {
107 return xstrdup(name);
109 } else {
110 if (name[1] == 0) {
111 return xstrdup(home_dir);
112 } else {
113 char *home;
115 home = get_home_dir(name + 1);
116 if (home)
117 return home;
118 return xstrdup(name);
121 } else {
122 return xstrdup(name);
126 /* view {{{ */
128 void view_clear(int view)
130 switch (view) {
131 case TREE_VIEW:
132 case SORTED_VIEW:
133 worker_remove_jobs(JOB_TYPE_LIB);
134 editable_lock();
135 editable_clear(&lib_editable);
137 /* FIXME: make this optional? */
138 lib_clear_store();
140 editable_unlock();
141 break;
142 case PLAYLIST_VIEW:
143 worker_remove_jobs(JOB_TYPE_PL);
144 editable_lock();
145 editable_clear(&pl_editable);
146 editable_unlock();
147 break;
148 case QUEUE_VIEW:
149 worker_remove_jobs(JOB_TYPE_QUEUE);
150 editable_lock();
151 editable_clear(&pq_editable);
152 editable_unlock();
153 break;
154 default:
155 info_msg(":clear only works in views 1-4");
159 void view_add(int view, char *arg, int prepend)
161 char *tmp, *name;
162 enum file_type ft;
164 tmp = expand_filename(arg);
165 ft = cmus_detect_ft(tmp, &name);
166 if (ft == FILE_TYPE_INVALID) {
167 error_msg("adding '%s': %s", tmp, strerror(errno));
168 free(tmp);
169 return;
171 free(tmp);
173 switch (view) {
174 case TREE_VIEW:
175 case SORTED_VIEW:
176 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
177 break;
178 case PLAYLIST_VIEW:
179 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
180 break;
181 case QUEUE_VIEW:
182 if (prepend) {
183 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
184 } else {
185 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
187 break;
188 default:
189 info_msg(":add only works in views 1-4");
191 free(name);
194 void view_load(int view, char *arg)
196 char *tmp, *name;
197 enum file_type ft;
199 tmp = expand_filename(arg);
200 ft = cmus_detect_ft(tmp, &name);
201 if (ft == FILE_TYPE_INVALID) {
202 error_msg("loading '%s': %s", tmp, strerror(errno));
203 free(tmp);
204 return;
206 free(tmp);
208 if (ft == FILE_TYPE_FILE)
209 ft = FILE_TYPE_PL;
210 if (ft != FILE_TYPE_PL) {
211 error_msg("loading '%s': not a playlist file", name);
212 free(name);
213 return;
216 switch (view) {
217 case TREE_VIEW:
218 case SORTED_VIEW:
219 worker_remove_jobs(JOB_TYPE_LIB);
220 editable_lock();
221 editable_clear(&lib_editable);
222 editable_unlock();
223 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
224 free(lib_filename);
225 lib_filename = name;
226 break;
227 case PLAYLIST_VIEW:
228 worker_remove_jobs(JOB_TYPE_PL);
229 editable_lock();
230 editable_clear(&pl_editable);
231 editable_unlock();
232 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
233 free(pl_filename);
234 pl_filename = name;
235 break;
236 default:
237 info_msg(":load only works in views 1-3");
238 free(name);
242 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
244 char *filename = *filenamep;
246 if (arg) {
247 free(filename);
248 filename = xstrdup(arg);
249 *filenamep = filename;
252 editable_lock();
253 if (cmus_save(for_each_ti, filename) == -1)
254 error_msg("saving '%s': %s", filename, strerror(errno));
255 editable_unlock();
258 void view_save(int view, char *arg)
260 if (arg) {
261 char *tmp;
263 tmp = expand_filename(arg);
264 arg = path_absolute(tmp);
265 free(tmp);
268 switch (view) {
269 case TREE_VIEW:
270 case SORTED_VIEW:
271 do_save(lib_for_each, arg, &lib_filename);
272 break;
273 case PLAYLIST_VIEW:
274 do_save(pl_for_each, arg, &pl_filename);
275 break;
276 default:
277 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
279 free(arg);
282 /* }}} */
284 /* only returns the last flag which is enough for it's callers */
285 static int parse_flags(const char **strp, const char *flags)
287 const char *str = *strp;
288 int flag = 0;
290 if (str == NULL)
291 return flag;
293 while (*str) {
294 if (*str != '-')
295 break;
297 // "-"
298 if (str[1] == 0)
299 break;
301 // "--" or "-- "
302 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
303 str += 2;
304 break;
307 // not "-?" or "-? "
308 if (str[2] && str[2] != ' ')
309 break;
311 flag = str[1];
312 if (!strchr(flags, flag)) {
313 error_msg("invalid option -%c", flag);
314 return -1;
317 str += 2;
319 while (*str == ' ')
320 str++;
322 while (*str == ' ')
323 str++;
324 if (*str == 0)
325 str = NULL;
326 *strp = str;
327 return flag;
330 static int flag_to_view(int flag)
332 switch (flag) {
333 case 'l':
334 return TREE_VIEW;
335 case 'p':
336 return PLAYLIST_VIEW;
337 case 'q':
338 case 'Q':
339 return QUEUE_VIEW;
340 default:
341 return cur_view;
345 static void cmd_add(char *arg)
347 int flag = parse_flags((const char **)&arg, "lpqQ");
349 if (flag == -1)
350 return;
351 if (arg == NULL) {
352 error_msg("not enough arguments\n");
353 return;
355 view_add(flag_to_view(flag), arg, flag == 'Q');
358 static void cmd_clear(char *arg)
360 int flag = parse_flags((const char **)&arg, "lpq");
362 if (flag == -1)
363 return;
364 if (arg) {
365 error_msg("too many arguments\n");
366 return;
368 view_clear(flag_to_view(flag));
371 static void cmd_load(char *arg)
373 int flag = parse_flags((const char **)&arg, "lp");
375 if (flag == -1)
376 return;
377 if (arg == NULL) {
378 error_msg("not enough arguments\n");
379 return;
381 view_load(flag_to_view(flag), arg);
384 static void cmd_save(char *arg)
386 int flag = parse_flags((const char **)&arg, "lp");
388 if (flag == -1)
389 return;
390 view_save(flag_to_view(flag), arg);
393 static void cmd_set(char *arg)
395 char *value = NULL;
396 int i;
398 for (i = 0; arg[i]; i++) {
399 if (arg[i] == '=') {
400 arg[i] = 0;
401 value = &arg[i + 1];
402 break;
405 if (value) {
406 option_set(arg, value);
407 help_win->changed = 1;
408 } else {
409 struct cmus_opt *opt;
410 char buf[OPTION_MAX_SIZE];
412 /* support "set <option>?" */
413 i--;
414 if (arg[i] == '?')
415 arg[i] = 0;
417 opt = option_find(arg);
418 if (opt) {
419 opt->get(opt->id, buf);
420 info_msg("setting: '%s=%s'", arg, buf);
425 static void cmd_toggle(char *arg)
427 struct cmus_opt *opt = option_find(arg);
429 if (opt == NULL)
430 return;
432 if (opt->toggle == NULL) {
433 error_msg("%s is not toggle option", opt->name);
434 return;
436 opt->toggle(opt->id);
437 help_win->changed = 1;
440 static int get_number(char *str, char **end)
442 int val = 0;
444 while (*str >= '0' && *str <= '9') {
445 val *= 10;
446 val += *str++ - '0';
448 *end = str;
449 return val;
452 static void cmd_seek(char *arg)
454 int relative = 0;
455 int seek = 0, sign = 1, count;
457 switch (*arg) {
458 case '-':
459 sign = -1;
460 case '+':
461 relative = 1;
462 arg++;
463 break;
466 count = 0;
467 goto inside;
469 do {
470 int num;
471 char *end;
473 if (*arg != ':')
474 break;
475 arg++;
476 inside:
477 num = get_number(arg, &end);
478 if (arg == end)
479 break;
480 arg = end;
481 seek = seek * 60 + num;
482 } while (++count < 3);
484 seek *= sign;
485 if (!count)
486 goto err;
488 if (count == 1) {
489 switch (tolower(*arg)) {
490 case 'h':
491 seek *= 60;
492 case 'm':
493 seek *= 60;
494 case 's':
495 arg++;
496 break;
500 if (!*arg) {
501 player_seek(seek, relative);
502 return;
504 err:
505 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
508 static void cmd_factivate(char *arg)
510 editable_lock();
511 filters_activate_names(arg);
512 editable_unlock();
515 static void cmd_filter(char *arg)
517 editable_lock();
518 filters_set_anonymous(arg);
519 editable_unlock();
522 static void cmd_fset(char *arg)
524 filters_set_filter(arg);
527 static void cmd_invert(char *arg)
529 editable_lock();
530 switch (cur_view) {
531 case SORTED_VIEW:
532 editable_invert_marks(&lib_editable);
533 break;
534 case PLAYLIST_VIEW:
535 editable_invert_marks(&pl_editable);
536 break;
537 case QUEUE_VIEW:
538 editable_invert_marks(&pq_editable);
539 break;
540 default:
541 info_msg(":invert only works in views 2-4");
543 editable_unlock();
546 static void cmd_mark(char *arg)
548 editable_lock();
549 switch (cur_view) {
550 case SORTED_VIEW:
551 editable_mark(&lib_editable, arg);
552 break;
553 case PLAYLIST_VIEW:
554 editable_mark(&pl_editable, arg);
555 break;
556 case QUEUE_VIEW:
557 editable_mark(&pq_editable, arg);
558 break;
559 default:
560 info_msg(":mark only works in views 2-4");
562 editable_unlock();
565 static void cmd_unmark(char *arg)
567 editable_lock();
568 switch (cur_view) {
569 case SORTED_VIEW:
570 editable_unmark(&lib_editable);
571 break;
572 case PLAYLIST_VIEW:
573 editable_unmark(&pl_editable);
574 break;
575 case QUEUE_VIEW:
576 editable_unmark(&pq_editable);
577 break;
578 default:
579 info_msg(":unmark only works in views 2-4");
581 editable_unlock();
584 static void cmd_cd(char *arg)
586 if (arg) {
587 char *dir, *absolute;
589 dir = expand_filename(arg);
590 absolute = path_absolute(dir);
591 if (chdir(dir) == -1) {
592 error_msg("could not cd to '%s': %s", dir, strerror(errno));
593 } else {
594 browser_chdir(absolute);
596 free(absolute);
597 free(dir);
598 } else {
599 if (chdir(home_dir) == -1) {
600 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
601 } else {
602 browser_chdir(home_dir);
607 static void cmd_bind(char *arg)
609 int flag = parse_flags((const char **)&arg, "f");
610 char *key, *func;
612 if (flag == -1)
613 return;
615 if (arg == NULL)
616 goto err;
618 key = strchr(arg, ' ');
619 if (key == NULL)
620 goto err;
621 *key++ = 0;
622 while (*key == ' ')
623 key++;
625 func = strchr(key, ' ');
626 if (func == NULL)
627 goto err;
628 *func++ = 0;
629 while (*func == ' ')
630 func++;
631 if (*func == 0)
632 goto err;
634 key_bind(arg, key, func, flag == 'f');
635 return;
636 err:
637 error_msg("expecting 3 arguments (context, key and function)\n");
640 static void cmd_unbind(char *arg)
642 int flag = parse_flags((const char **)&arg, "f");
643 char *key;
645 if (flag == -1)
646 return;
648 if (arg == NULL)
649 goto err;
651 key = strchr(arg, ' ');
652 if (key == NULL)
653 goto err;
654 *key++ = 0;
655 while (*key == ' ')
656 key++;
657 if (*key == 0)
658 goto err;
660 /* FIXME: remove spaces at end */
662 key_unbind(arg, key, flag == 'f');
663 return;
664 err:
665 error_msg("expecting 2 arguments (context and key)\n");
668 static void cmd_showbind(char *arg)
670 char *key;
672 key = strchr(arg, ' ');
673 if (key == NULL)
674 goto err;
675 *key++ = 0;
676 while (*key == ' ')
677 key++;
678 if (*key == 0)
679 goto err;
681 /* FIXME: remove spaces at end */
683 show_binding(arg, key);
684 return;
685 err:
686 error_msg("expecting 2 arguments (context and key)\n");
689 static void cmd_quit(char *arg)
691 quit();
694 static void cmd_reshuffle(char *arg)
696 editable_lock();
697 lib_reshuffle();
698 pl_reshuffle();
699 editable_unlock();
702 static void cmd_source(char *arg)
704 char *filename = expand_filename(arg);
706 if (source_file(filename) == -1)
707 error_msg("sourcing %s: %s", filename, strerror(errno));
708 free(filename);
711 static void cmd_colorscheme(char *arg)
713 char filename[512];
715 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
716 if (source_file(filename) == -1) {
717 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
718 if (source_file(filename) == -1)
719 error_msg("sourcing %s: %s", filename, strerror(errno));
724 * \" inside double-quotes becomes "
725 * \\ inside double-quotes becomes \
727 static char *parse_quoted(const char **strp)
729 const char *str = *strp;
730 const char *start;
731 char *ret, *dst;
733 str++;
734 start = str;
735 while (1) {
736 int c = *str++;
738 if (c == 0)
739 goto error;
740 if (c == '"')
741 break;
742 if (c == '\\') {
743 if (*str++ == 0)
744 goto error;
747 *strp = str;
748 ret = xnew(char, str - start);
749 str = start;
750 dst = ret;
751 while (1) {
752 int c = *str++;
754 if (c == '"')
755 break;
756 if (c == '\\') {
757 c = *str++;
758 if (c != '"' && c != '\\')
759 *dst++ = '\\';
761 *dst++ = c;
763 *dst = 0;
764 return ret;
765 error:
766 error_msg("`\"' expected");
767 return NULL;
770 static char *parse_escaped(const char **strp)
772 const char *str = *strp;
773 const char *start;
774 char *ret, *dst;
776 start = str;
777 while (1) {
778 int c = *str;
780 if (c == 0 || c == ' ' || c == '\'' || c == '"')
781 break;
783 str++;
784 if (c == '\\') {
785 c = *str;
786 if (c == 0)
787 break;
788 str++;
791 *strp = str;
792 ret = xnew(char, str - start + 1);
793 str = start;
794 dst = ret;
795 while (1) {
796 int c = *str;
798 if (c == 0 || c == ' ' || c == '\'' || c == '"')
799 break;
801 str++;
802 if (c == '\\') {
803 c = *str;
804 if (c == 0) {
805 *dst++ = '\\';
806 break;
808 str++;
810 *dst++ = c;
812 *dst = 0;
813 return ret;
816 static char *parse_one(const char **strp)
818 const char *str = *strp;
819 char *ret = NULL;
821 while (1) {
822 char *part;
823 int c = *str;
825 if (!c || c == ' ')
826 break;
827 if (c == '"') {
828 part = parse_quoted(&str);
829 if (part == NULL)
830 goto error;
831 } else if (c == '\'') {
832 /* backslashes are normal chars inside single-quotes */
833 const char *end;
835 str++;
836 end = strchr(str, '\'');
837 if (end == NULL)
838 goto sq_missing;
839 part = xstrndup(str, end - str);
840 str = end + 1;
841 } else {
842 part = parse_escaped(&str);
845 if (ret == NULL) {
846 ret = part;
847 } else {
848 char *tmp = xstrjoin(ret, part);
849 free(ret);
850 ret = tmp;
853 *strp = str;
854 return ret;
855 sq_missing:
856 error_msg("`'' expected");
857 error:
858 free(ret);
859 return NULL;
862 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
864 char **av = NULL;
865 int nr = 0;
866 int alloc = 0;
868 while (*cmd) {
869 char *arg;
871 /* there can't be spaces at start of command
872 * and there is at least one argument */
873 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
874 /* {} is replaced with file arguments */
875 if (*args_idx != -1)
876 goto only_once_please;
877 *args_idx = nr;
878 cmd += 2;
879 goto skip_spaces;
880 } else {
881 arg = parse_one(&cmd);
882 if (arg == NULL)
883 goto error;
886 if (nr == alloc) {
887 alloc = alloc ? alloc * 2 : 4;
888 av = xrenew(char *, av, alloc + 1);
890 av[nr++] = arg;
891 skip_spaces:
892 while (*cmd == ' ')
893 cmd++;
895 av[nr] = NULL;
896 *ac = nr;
897 return av;
898 only_once_please:
899 error_msg("{} can be used only once");
900 error:
901 while (nr > 0)
902 free(av[--nr]);
903 free(av);
904 return NULL;
907 static struct track_info **sel_tis;
908 static int sel_tis_alloc;
909 static int sel_tis_nr;
911 static int add_ti(void *data, struct track_info *ti)
913 if (sel_tis_nr == sel_tis_alloc) {
914 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
915 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
917 track_info_ref(ti);
918 sel_tis[sel_tis_nr++] = ti;
919 return 0;
922 static void cmd_run(char *arg)
924 char **av, **argv;
925 int ac, argc, i, run, files_idx = -1;
927 if (cur_view > QUEUE_VIEW) {
928 info_msg("Command execution is supported only in views 1-4");
929 return;
932 av = parse_cmd(arg, &files_idx, &ac);
933 if (av == NULL) {
934 return;
937 /* collect selected files (struct track_info) */
938 sel_tis = NULL;
939 sel_tis_alloc = 0;
940 sel_tis_nr = 0;
942 editable_lock();
943 switch (cur_view) {
944 case TREE_VIEW:
945 __tree_for_each_sel(add_ti, NULL, 0);
946 break;
947 case SORTED_VIEW:
948 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
949 break;
950 case PLAYLIST_VIEW:
951 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
952 break;
953 case QUEUE_VIEW:
954 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
955 break;
957 editable_unlock();
959 if (sel_tis_nr == 0) {
960 /* no files selected, do nothing */
961 free_str_array(av);
962 return;
964 sel_tis[sel_tis_nr] = NULL;
966 /* build argv */
967 argv = xnew(char *, ac + sel_tis_nr + 1);
968 argc = 0;
969 if (files_idx == -1) {
970 /* add selected files after rest of the args */
971 for (i = 0; i < ac; i++)
972 argv[argc++] = av[i];
973 for (i = 0; i < sel_tis_nr; i++)
974 argv[argc++] = sel_tis[i]->filename;
975 } else {
976 for (i = 0; i < files_idx; i++)
977 argv[argc++] = av[i];
978 for (i = 0; i < sel_tis_nr; i++)
979 argv[argc++] = sel_tis[i]->filename;
980 for (i = files_idx; i < ac; i++)
981 argv[argc++] = av[i];
983 argv[argc] = NULL;
985 for (i = 0; argv[i]; i++)
986 d_print("ARG: '%s'\n", argv[i]);
988 run = 1;
989 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
990 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
991 info_msg("Aborted");
992 run = 0;
995 if (run) {
996 int status;
998 if (spawn(argv, &status)) {
999 error_msg("executing %s: %s", argv[0], strerror(errno));
1000 } else {
1001 if (WIFEXITED(status)) {
1002 int rc = WEXITSTATUS(status);
1004 if (rc)
1005 error_msg("%s returned %d", argv[0], rc);
1007 if (WIFSIGNALED(status))
1008 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1010 switch (cur_view) {
1011 case TREE_VIEW:
1012 case SORTED_VIEW:
1013 /* this must be done before sel_tis are unreffed */
1014 free_str_array(av);
1015 free(argv);
1017 /* remove non-existed files, update tags for changed files */
1018 cmus_update_tis(sel_tis, sel_tis_nr);
1020 /* we don't own sel_tis anymore! */
1021 return;
1025 free_str_array(av);
1026 free(argv);
1027 for (i = 0; sel_tis[i]; i++)
1028 track_info_unref(sel_tis[i]);
1029 free(sel_tis);
1032 static int get_one_ti(void *data, struct track_info *ti)
1034 struct track_info **sel_ti = data;
1036 track_info_ref(ti);
1037 *sel_ti = ti;
1038 /* stop the for each loop, we need only the first selected track */
1039 return 1;
1042 static void cmd_echo(char *arg)
1044 struct track_info *sel_ti;
1045 char *ptr = arg;
1047 while (1) {
1048 ptr = strchr(ptr, '{');
1049 if (ptr == NULL)
1050 break;
1051 if (ptr[1] == '}')
1052 break;
1053 ptr++;
1056 if (ptr == NULL) {
1057 info_msg("%s", arg);
1058 return;
1061 if (cur_view > QUEUE_VIEW) {
1062 info_msg("echo with {} in its arguments is supported only in views 1-4");
1063 return;
1066 *ptr = 0;
1067 ptr += 2;
1069 /* get only the first selected track */
1070 sel_ti = NULL;
1072 editable_lock();
1073 switch (cur_view) {
1074 case TREE_VIEW:
1075 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1076 break;
1077 case SORTED_VIEW:
1078 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1079 break;
1080 case PLAYLIST_VIEW:
1081 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1082 break;
1083 case QUEUE_VIEW:
1084 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1085 break;
1087 editable_unlock();
1089 if (sel_ti == NULL)
1090 return;
1092 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1093 track_info_unref(sel_ti);
1096 #define VF_RELATIVE 0x01
1097 #define VF_PERCENTAGE 0x02
1099 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1101 unsigned int f = 0;
1102 int ch, val = 0, digits = 0, sign = 1;
1104 if (*arg == '-') {
1105 arg++;
1106 f |= VF_RELATIVE;
1107 sign = -1;
1108 } else if (*arg == '+') {
1109 arg++;
1110 f |= VF_RELATIVE;
1113 while (1) {
1114 ch = *arg++;
1115 if (ch < '0' || ch > '9')
1116 break;
1117 val *= 10;
1118 val += ch - '0';
1119 digits++;
1121 if (digits == 0)
1122 goto err;
1124 if (ch == '%') {
1125 f |= VF_PERCENTAGE;
1126 ch = *arg;
1128 if (ch)
1129 goto err;
1131 *value = sign * val;
1132 *flags = f;
1133 return 0;
1134 err:
1135 return -1;
1138 static int calc_vol(int val, int old, unsigned int flags)
1140 if (flags & VF_RELATIVE) {
1141 if (flags & VF_PERCENTAGE)
1142 val = scale_from_percentage(val, volume_max);
1143 val += old;
1144 } else if (flags & VF_PERCENTAGE) {
1145 val = scale_from_percentage(val, volume_max);
1147 return clamp(val, 0, volume_max);
1151 * :vol value [value]
1153 * where value is [-+]?[0-9]+%?
1155 static void cmd_vol(char *arg)
1157 char **values = get_words(arg);
1158 unsigned int lf, rf;
1159 int l, r, ol, or;
1161 if (values[1] && values[2])
1162 goto err;
1164 if (parse_vol_arg(values[0], &l, &lf))
1165 goto err;
1167 r = l;
1168 rf = lf;
1169 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1170 goto err;
1172 free_str_array(values);
1174 player_get_volume(&ol, &or);
1175 l = calc_vol(l, ol, lf);
1176 r = calc_vol(r, or, rf);
1177 player_set_volume(l, r);
1178 return;
1179 err:
1180 free_str_array(values);
1181 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1184 static void cmd_view(char *arg)
1186 int view;
1188 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view))
1189 set_view(view - 1);
1192 static void cmd_p_next(char *arg)
1194 cmus_next();
1197 static void cmd_p_pause(char *arg)
1199 player_pause();
1202 static void cmd_p_play(char *arg)
1204 if (arg) {
1205 cmus_play_file(arg);
1206 } else {
1207 player_play();
1211 static void cmd_p_prev(char *arg)
1213 cmus_prev();
1216 static void cmd_p_stop(char *arg)
1218 player_stop();
1221 static void cmd_search_next(char *arg)
1223 if (search_str) {
1224 if (!search_next(searchable, search_str, search_direction))
1225 search_not_found();
1229 static void cmd_search_prev(char *arg)
1231 if (search_str) {
1232 if (!search_next(searchable, search_str, !search_direction))
1233 search_not_found();
1237 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1239 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1242 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1244 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1247 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1249 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1252 static for_each_sel_ti_cb view_for_each_sel[4] = {
1253 tree_for_each_sel,
1254 sorted_for_each_sel,
1255 pl_for_each_sel,
1256 pq_for_each_sel
1259 /* wrapper for void lib_add_track(struct track_info *) etc. */
1260 static int wrapper_cb(void *data, struct track_info *ti)
1262 add_ti_cb add = data;
1264 add(ti);
1265 return 0;
1268 static void add_from_browser(add_ti_cb add, int job_type)
1270 char *sel = browser_get_sel();
1272 if (sel) {
1273 enum file_type ft;
1274 char *ret;
1276 ft = cmus_detect_ft(sel, &ret);
1277 if (ft != FILE_TYPE_INVALID) {
1278 cmus_add(add, ret, ft, job_type);
1279 window_down(browser_win, 1);
1281 free(ret);
1282 free(sel);
1286 static void cmd_win_add_l(char *arg)
1288 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1289 return;
1291 if (cur_view <= QUEUE_VIEW) {
1292 editable_lock();
1293 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1294 editable_unlock();
1295 } else if (cur_view == BROWSER_VIEW) {
1296 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1300 static void cmd_win_add_p(char *arg)
1302 /* could allow adding dups? */
1303 if (cur_view == PLAYLIST_VIEW)
1304 return;
1306 if (cur_view <= QUEUE_VIEW) {
1307 editable_lock();
1308 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1309 editable_unlock();
1310 } else if (cur_view == BROWSER_VIEW) {
1311 add_from_browser(pl_add_track, JOB_TYPE_PL);
1315 static void cmd_win_add_Q(char *arg)
1317 if (cur_view == QUEUE_VIEW)
1318 return;
1320 if (cur_view <= QUEUE_VIEW) {
1321 editable_lock();
1322 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1323 editable_unlock();
1324 } else if (cur_view == BROWSER_VIEW) {
1325 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1329 static void cmd_win_add_q(char *arg)
1331 if (cur_view == QUEUE_VIEW)
1332 return;
1334 if (cur_view <= QUEUE_VIEW) {
1335 editable_lock();
1336 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1337 editable_unlock();
1338 } else if (cur_view == BROWSER_VIEW) {
1339 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1343 static void cmd_win_activate(char *arg)
1345 struct track_info *info = NULL;
1347 editable_lock();
1348 switch (cur_view) {
1349 case TREE_VIEW:
1350 info = tree_set_selected();
1351 break;
1352 case SORTED_VIEW:
1353 info = sorted_set_selected();
1354 break;
1355 case PLAYLIST_VIEW:
1356 info = pl_set_selected();
1357 break;
1358 case QUEUE_VIEW:
1359 break;
1360 case BROWSER_VIEW:
1361 browser_enter();
1362 break;
1363 case FILTERS_VIEW:
1364 filters_activate();
1365 break;
1366 case HELP_VIEW:
1367 help_select();
1368 break;
1370 editable_unlock();
1372 if (info) {
1373 /* update lib/pl mode */
1374 if (cur_view < 2)
1375 play_library = 1;
1376 if (cur_view == 2)
1377 play_library = 0;
1379 player_play_file(info);
1383 static void cmd_win_mv_after(char *arg)
1385 editable_lock();
1386 switch (cur_view) {
1387 case TREE_VIEW:
1388 break;
1389 case SORTED_VIEW:
1390 editable_move_after(&lib_editable);
1391 break;
1392 case PLAYLIST_VIEW:
1393 editable_move_after(&pl_editable);
1394 break;
1395 case QUEUE_VIEW:
1396 editable_move_after(&pq_editable);
1397 break;
1398 case BROWSER_VIEW:
1399 break;
1400 case FILTERS_VIEW:
1401 break;
1402 case HELP_VIEW:
1403 break;
1405 editable_unlock();
1408 static void cmd_win_mv_before(char *arg)
1410 editable_lock();
1411 switch (cur_view) {
1412 case TREE_VIEW:
1413 break;
1414 case SORTED_VIEW:
1415 editable_move_before(&lib_editable);
1416 break;
1417 case PLAYLIST_VIEW:
1418 editable_move_before(&pl_editable);
1419 break;
1420 case QUEUE_VIEW:
1421 editable_move_before(&pq_editable);
1422 break;
1423 case BROWSER_VIEW:
1424 break;
1425 case FILTERS_VIEW:
1426 break;
1427 case HELP_VIEW:
1428 break;
1430 editable_unlock();
1433 static void cmd_win_remove(char *arg)
1435 editable_lock();
1436 switch (cur_view) {
1437 case TREE_VIEW:
1438 tree_remove_sel();
1439 break;
1440 case SORTED_VIEW:
1441 editable_remove_sel(&lib_editable);
1442 break;
1443 case PLAYLIST_VIEW:
1444 editable_remove_sel(&pl_editable);
1445 break;
1446 case QUEUE_VIEW:
1447 editable_remove_sel(&pq_editable);
1448 break;
1449 case BROWSER_VIEW:
1450 browser_delete();
1451 break;
1452 case FILTERS_VIEW:
1453 filters_delete_filter();
1454 break;
1455 case HELP_VIEW:
1456 help_remove();
1457 break;
1459 editable_unlock();
1462 static void cmd_win_sel_cur(char *arg)
1464 editable_lock();
1465 switch (cur_view) {
1466 case TREE_VIEW:
1467 tree_sel_current();
1468 break;
1469 case SORTED_VIEW:
1470 sorted_sel_current();
1471 break;
1472 case PLAYLIST_VIEW:
1473 pl_sel_current();
1474 break;
1475 case QUEUE_VIEW:
1476 break;
1477 case BROWSER_VIEW:
1478 break;
1479 case FILTERS_VIEW:
1480 break;
1481 case HELP_VIEW:
1482 break;
1484 editable_unlock();
1487 static void cmd_win_toggle(char *arg)
1489 switch (cur_view) {
1490 case TREE_VIEW:
1491 editable_lock();
1492 tree_toggle_expand_artist();
1493 editable_unlock();
1494 break;
1495 case SORTED_VIEW:
1496 editable_lock();
1497 editable_toggle_mark(&lib_editable);
1498 editable_unlock();
1499 break;
1500 case PLAYLIST_VIEW:
1501 editable_lock();
1502 editable_toggle_mark(&pl_editable);
1503 editable_unlock();
1504 break;
1505 case QUEUE_VIEW:
1506 editable_lock();
1507 editable_toggle_mark(&pq_editable);
1508 editable_unlock();
1509 break;
1510 case BROWSER_VIEW:
1511 break;
1512 case FILTERS_VIEW:
1513 filters_toggle_filter();
1514 break;
1515 case HELP_VIEW:
1516 help_toggle();
1517 break;
1521 static struct window *current_win(void)
1523 switch (cur_view) {
1524 case TREE_VIEW:
1525 return lib_cur_win;
1526 case SORTED_VIEW:
1527 return lib_editable.win;
1528 case PLAYLIST_VIEW:
1529 return pl_editable.win;
1530 case QUEUE_VIEW:
1531 return pq_editable.win;
1532 case BROWSER_VIEW:
1533 return browser_win;
1534 case HELP_VIEW:
1535 return help_win;
1536 case FILTERS_VIEW:
1537 default:
1538 return filters_win;
1542 static void cmd_win_bottom(char *arg)
1544 editable_lock();
1545 window_goto_bottom(current_win());
1546 editable_unlock();
1549 static void cmd_win_down(char *arg)
1551 editable_lock();
1552 window_down(current_win(), 1);
1553 editable_unlock();
1556 static void cmd_win_next(char *arg)
1558 if (cur_view == TREE_VIEW) {
1559 editable_lock();
1560 tree_toggle_active_window();
1561 editable_unlock();
1565 static void cmd_win_pg_down(char *arg)
1567 editable_lock();
1568 window_page_down(current_win());
1569 editable_unlock();
1572 static void cmd_win_pg_up(char *arg)
1574 editable_lock();
1575 window_page_up(current_win());
1576 editable_unlock();
1579 static void cmd_win_top(char *arg)
1581 editable_lock();
1582 window_goto_top(current_win());
1583 editable_unlock();
1586 static void cmd_win_up(char *arg)
1588 editable_lock();
1589 window_up(current_win(), 1);
1590 editable_unlock();
1593 static void cmd_win_update(char *arg)
1595 switch (cur_view) {
1596 case TREE_VIEW:
1597 case SORTED_VIEW:
1598 cmus_update_lib();
1599 break;
1600 case BROWSER_VIEW:
1601 browser_reload();
1602 break;
1606 static void cmd_browser_up(char *arg)
1608 browser_up();
1611 static void cmd_refresh(char *arg)
1613 clearok(curscr, TRUE);
1614 refresh();
1617 static int cmp_intp(const void *ap, const void *bp)
1619 int a = *(int *)ap;
1620 int b = *(int *)bp;
1621 return a - b;
1624 static int *rand_array(int size, int nmax)
1626 int *r = xnew(int, size + 1);
1627 int i, offset = 0;
1628 int count = size;
1630 if (count > nmax / 2) {
1632 * Imagine that there are 1000 tracks in library and we want to
1633 * add 998 random tracks to queue. After we have added 997
1634 * random numbers to the array it would be quite hard to find a
1635 * random number that isn't already in the array (3/1000
1636 * probability).
1638 * So we invert the logic:
1640 * Find two (1000 - 998) random numbers in 0..999 range and put
1641 * them at end of the array. Sort the numbers and then fill
1642 * the array starting at index 0 with incrementing values that
1643 * are not in the set of random numbers.
1645 count = nmax - count;
1646 offset = size - count;
1649 for (i = 0; i < count; ) {
1650 int v, j;
1651 found:
1652 v = rand() % nmax;
1653 for (j = 0; j < i; j++) {
1654 if (r[offset + j] == v)
1655 goto found;
1657 r[offset + i++] = v;
1659 qsort(r + offset, count, sizeof(*r), cmp_intp);
1661 if (offset) {
1662 int j, n;
1664 /* simplifies next loop */
1665 r[size] = nmax;
1667 /* convert the indexes we don't want to those we want */
1668 i = 0;
1669 j = offset;
1670 n = 0;
1671 do {
1672 while (n < r[j])
1673 r[i++] = n++;
1674 j++;
1675 n++;
1676 } while (i < size);
1678 return r;
1681 static int count_albums(void)
1683 struct artist *artist;
1684 struct list_head *item;
1685 int count = 0;
1687 list_for_each_entry(artist, &lib_artist_head, node) {
1688 list_for_each(item, &artist->album_head)
1689 count++;
1691 return count;
1694 struct album_list {
1695 struct list_head node;
1696 const struct album *album;
1699 static void cmd_lqueue(char *arg)
1701 LIST_HEAD(head);
1702 const struct list_head *item;
1703 const struct album *album;
1704 int count = 1, nmax, i, pos;
1705 int *r;
1707 if (arg) {
1708 long int val;
1710 if (str_to_int(arg, &val) || val <= 0) {
1711 error_msg("argument must be positive integer");
1712 return;
1714 count = val;
1716 nmax = count_albums();
1717 if (count > nmax)
1718 count = nmax;
1719 if (!count)
1720 return;
1722 r = rand_array(count, nmax);
1723 album = to_album(to_artist(lib_artist_head.next)->album_head.next);
1724 pos = 0;
1725 for (i = 0; i < count; i++) {
1726 struct album_list *a;
1728 while (pos < r[i]) {
1729 struct artist *artist = album->artist;
1730 if (album->node.next == &artist->album_head) {
1731 artist = to_artist(artist->node.next);
1732 album = to_album(artist->album_head.next);
1733 } else {
1734 album = to_album(album->node.next);
1736 pos++;
1738 a = xnew(struct album_list, 1);
1739 a->album = album;
1740 list_add_rand(&head, &a->node, i);
1742 free(r);
1744 item = head.next;
1745 do {
1746 struct list_head *next = item->next;
1747 struct album_list *a = container_of(item, struct album_list, node);
1748 struct tree_track *t;
1750 list_for_each_entry(t, &a->album->track_head, node)
1751 editable_add(&pq_editable, simple_track_new(tree_track_info(t)));
1752 free(a);
1753 item = next;
1754 } while (item != &head);
1757 static void cmd_tqueue(char *arg)
1759 LIST_HEAD(head);
1760 struct list_head *item;
1761 int count = 1, i, pos;
1762 int *r;
1764 if (arg) {
1765 long int val;
1767 if (str_to_int(arg, &val) || val <= 0) {
1768 error_msg("argument must be positive integer");
1769 return;
1771 count = val;
1773 if (count > lib_editable.nr_tracks)
1774 count = lib_editable.nr_tracks;
1775 if (!count)
1776 return;
1778 r = rand_array(count, lib_editable.nr_tracks);
1779 item = lib_editable.head.next;
1780 pos = 0;
1781 for (i = 0; i < count; i++) {
1782 struct simple_track *t;
1784 while (pos < r[i]) {
1785 item = item->next;
1786 pos++;
1788 t = simple_track_new(to_simple_track(item)->info);
1789 list_add_rand(&head, &t->node, i);
1791 free(r);
1793 item = head.next;
1794 do {
1795 struct list_head *next = item->next;
1796 struct simple_track *t = to_simple_track(item);
1797 editable_add(&pq_editable, t);
1798 item = next;
1799 } while (item != &head);
1802 /* tab exp {{{
1804 * these functions fill tabexp struct, which is resetted beforehand
1807 /* buffer used for tab expansion */
1808 static char expbuf[512];
1810 static int filter_directories(const char *name, const struct stat *s)
1812 return S_ISDIR(s->st_mode);
1815 static int filter_any(const char *name, const struct stat *s)
1817 return 1;
1820 static int filter_playable(const char *name, const struct stat *s)
1822 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1825 static int filter_playlist(const char *name, const struct stat *s)
1827 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1830 static int filter_supported(const char *name, const struct stat *s)
1832 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1835 static void expand_files(const char *str)
1837 expand_files_and_dirs(str, filter_any);
1840 static void expand_directories(const char *str)
1842 expand_files_and_dirs(str, filter_directories);
1845 static void expand_playable(const char *str)
1847 expand_files_and_dirs(str, filter_playable);
1850 static void expand_playlist(const char *str)
1852 expand_files_and_dirs(str, filter_playlist);
1855 static void expand_supported(const char *str)
1857 expand_files_and_dirs(str, filter_supported);
1860 static void expand_add(const char *str)
1862 int flag = parse_flags(&str, "lpqQ");
1864 if (flag == -1)
1865 return;
1866 if (str == NULL)
1867 str = "";
1868 expand_supported(str);
1870 if (tabexp.head && flag) {
1871 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1872 free(tabexp.head);
1873 tabexp.head = xstrdup(expbuf);
1877 static void expand_load_save(const char *str)
1879 int flag = parse_flags(&str, "lp");
1881 if (flag == -1)
1882 return;
1883 if (str == NULL)
1884 str = "";
1885 expand_playlist(str);
1887 if (tabexp.head && flag) {
1888 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1889 free(tabexp.head);
1890 tabexp.head = xstrdup(expbuf);
1894 static void expand_key_context(const char *str, const char *force)
1896 int pos, i, len = strlen(str);
1897 char **tails;
1899 tails = xnew(char *, NR_CTXS + 1);
1900 pos = 0;
1901 for (i = 0; key_context_names[i]; i++) {
1902 int cmp = strncmp(str, key_context_names[i], len);
1903 if (cmp > 0)
1904 continue;
1905 if (cmp < 0)
1906 break;
1907 tails[pos++] = xstrdup(key_context_names[i] + len);
1910 if (pos == 0) {
1911 free(tails);
1912 return;
1914 if (pos == 1) {
1915 char *tmp = xstrjoin(tails[0], " ");
1916 free(tails[0]);
1917 tails[0] = tmp;
1919 tails[pos] = NULL;
1920 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1921 tabexp.head = xstrdup(expbuf);
1922 tabexp.tails = tails;
1925 static int get_context(const char *str, int len)
1927 int i, c = -1, count = 0;
1929 for (i = 0; key_context_names[i]; i++) {
1930 if (strncmp(str, key_context_names[i], len) == 0) {
1931 if (key_context_names[i][len] == 0) {
1932 /* exact */
1933 return i;
1935 c = i;
1936 count++;
1939 if (count == 1)
1940 return c;
1941 return -1;
1944 static void expand_command_line(const char *str);
1946 static void expand_bind_args(const char *str)
1948 /* :bind context key function
1950 * possible values for str:
1952 * context k
1953 * context key f
1955 * you need to know context before you can expand function
1957 /* start and end pointers for context, key and function */
1958 const char *cs, *ce, *ks, *ke, *fs;
1959 int i, c, k, count;
1960 int flag = parse_flags((const char **)&str, "f");
1961 const char *force = "";
1963 if (flag == -1)
1964 return;
1965 if (str == NULL)
1966 str = "";
1968 if (flag == 'f')
1969 force = "-f ";
1971 cs = str;
1972 ce = strchr(cs, ' ');
1973 if (ce == NULL) {
1974 expand_key_context(cs, force);
1975 return;
1978 /* context must be expandable */
1979 c = get_context(cs, ce - cs);
1980 if (c == -1) {
1981 /* context is ambiguous or invalid */
1982 return;
1985 ks = ce;
1986 while (*ks == ' ')
1987 ks++;
1988 ke = strchr(ks, ' ');
1989 if (ke == NULL) {
1990 /* expand key */
1991 int len = strlen(ks);
1992 PTR_ARRAY(array);
1994 for (i = 0; key_table[i].name; i++) {
1995 int cmp = strncmp(ks, key_table[i].name, len);
1996 if (cmp > 0)
1997 continue;
1998 if (cmp < 0)
1999 break;
2000 ptr_array_add(&array, xstrdup(key_table[i].name + len));
2003 if (!array.count)
2004 return;
2006 if (array.count == 1) {
2007 char **ptrs = array.ptrs;
2008 char *tmp = xstrjoin(ptrs[0], " ");
2009 free(ptrs[0]);
2010 ptrs[0] = tmp;
2013 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
2015 ptr_array_plug(&array);
2016 tabexp.head = xstrdup(expbuf);
2017 tabexp.tails = array.ptrs;
2018 return;
2021 /* key must be expandable */
2022 k = -1;
2023 count = 0;
2024 for (i = 0; key_table[i].name; i++) {
2025 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
2026 if (key_table[i].name[ke - ks] == 0) {
2027 /* exact */
2028 k = i;
2029 count = 1;
2030 break;
2032 k = i;
2033 count++;
2036 if (count != 1) {
2037 /* key is ambiguous or invalid */
2038 return;
2041 fs = ke;
2042 while (*fs == ' ')
2043 fs++;
2045 if (*fs == ':')
2046 fs++;
2048 /* expand com [arg...] */
2049 expand_command_line(fs);
2050 if (tabexp.head == NULL) {
2051 /* command expand failed */
2052 return;
2056 * tabexp.head is now "com"
2057 * tabexp.tails is [ mand1 mand2 ... ]
2059 * need to change tabexp.head to "context key com"
2062 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
2063 key_table[k].name, tabexp.head);
2064 free(tabexp.head);
2065 tabexp.head = xstrdup(expbuf);
2068 static void expand_unbind_args(const char *str)
2070 /* :unbind context key */
2071 /* start and end pointers for context and key */
2072 const char *cs, *ce, *ks;
2073 const struct binding *b;
2074 PTR_ARRAY(array);
2075 int c, len;
2077 cs = str;
2078 ce = strchr(cs, ' ');
2079 if (ce == NULL) {
2080 expand_key_context(cs, "");
2081 return;
2084 /* context must be expandable */
2085 c = get_context(cs, ce - cs);
2086 if (c == -1) {
2087 /* context is ambiguous or invalid */
2088 return;
2091 ks = ce;
2092 while (*ks == ' ')
2093 ks++;
2095 /* expand key */
2096 len = strlen(ks);
2097 b = key_bindings[c];
2098 while (b) {
2099 if (!strncmp(ks, b->key->name, len))
2100 ptr_array_add(&array, xstrdup(b->key->name + len));
2101 b = b->next;
2103 if (!array.count)
2104 return;
2106 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
2108 ptr_array_plug(&array);
2109 tabexp.head = xstrdup(expbuf);
2110 tabexp.tails = array.ptrs;
2113 static void expand_factivate(const char *str)
2115 /* "name1 name2 name3", expand only name3 */
2116 struct filter_entry *e;
2117 const char *name;
2118 PTR_ARRAY(array);
2119 int str_len, len, i;
2121 str_len = strlen(str);
2122 i = str_len;
2123 while (i > 0) {
2124 if (str[i - 1] == ' ')
2125 break;
2126 i--;
2128 len = str_len - i;
2129 name = str + i;
2131 list_for_each_entry(e, &filters_head, node) {
2132 if (!strncmp(name, e->name, len))
2133 ptr_array_add(&array, xstrdup(e->name + len));
2135 if (!array.count)
2136 return;
2138 ptr_array_plug(&array);
2139 tabexp.head = xstrdup(str);
2140 tabexp.tails = array.ptrs;
2143 static void expand_options(const char *str)
2145 struct cmus_opt *opt;
2146 int len;
2147 char **tails;
2149 /* tabexp is resetted */
2150 len = strlen(str);
2151 if (len > 1 && str[len - 1] == '=') {
2152 /* expand value */
2153 char *var = xstrndup(str, len - 1);
2155 list_for_each_entry(opt, &option_head, node) {
2156 if (strcmp(var, opt->name) == 0) {
2157 char buf[OPTION_MAX_SIZE];
2159 tails = xnew(char *, 2);
2161 buf[0] = 0;
2162 opt->get(opt->id, buf);
2163 tails[0] = xstrdup(buf);
2164 tails[1] = NULL;
2166 tabexp.head = xstrdup(str);
2167 tabexp.tails = tails;
2168 free(var);
2169 return;
2172 free(var);
2173 } else {
2174 /* expand variable */
2175 int pos;
2177 tails = xnew(char *, nr_options + 1);
2178 pos = 0;
2179 list_for_each_entry(opt, &option_head, node) {
2180 if (strncmp(str, opt->name, len) == 0)
2181 tails[pos++] = xstrdup(opt->name + len);
2183 if (pos > 0) {
2184 if (pos == 1) {
2185 /* only one variable matches, add '=' */
2186 char *tmp = xstrjoin(tails[0], "=");
2188 free(tails[0]);
2189 tails[0] = tmp;
2192 tails[pos] = NULL;
2193 tabexp.head = xstrdup(str);
2194 tabexp.tails = tails;
2195 } else {
2196 free(tails);
2201 static void expand_toptions(const char *str)
2203 struct cmus_opt *opt;
2204 int len, pos;
2205 char **tails;
2207 tails = xnew(char *, nr_options + 1);
2208 len = strlen(str);
2209 pos = 0;
2210 list_for_each_entry(opt, &option_head, node) {
2211 if (opt->toggle == NULL)
2212 continue;
2213 if (strncmp(str, opt->name, len) == 0)
2214 tails[pos++] = xstrdup(opt->name + len);
2216 if (pos > 0) {
2217 tails[pos] = NULL;
2218 tabexp.head = xstrdup(str);
2219 tabexp.tails = tails;
2220 } else {
2221 free(tails);
2225 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2227 struct directory dir;
2228 const char *name, *dot;
2229 int len = strlen(str);
2231 if (dir_open(&dir, dirname))
2232 return;
2234 while ((name = dir_read(&dir))) {
2235 if (!S_ISREG(dir.st.st_mode))
2236 continue;
2237 if (strncmp(name, str, len))
2238 continue;
2239 dot = strrchr(name, '.');
2240 if (dot == NULL || strcmp(dot, ".theme"))
2241 continue;
2242 if (dot - name < len)
2243 /* str is "foo.th"
2244 * matches "foo.theme"
2245 * which also ends with ".theme"
2247 continue;
2248 ptr_array_add(array, xstrndup(name + len, dot - name - len));
2250 dir_close(&dir);
2253 static void expand_colorscheme(const char *str)
2255 PTR_ARRAY(array);
2257 load_themes(cmus_config_dir, str, &array);
2258 load_themes(DATADIR "/cmus", str, &array);
2260 if (array.count) {
2261 ptr_array_sort(&array, strptrcmp);
2263 ptr_array_plug(&array);
2264 tabexp.head = xstrdup(str);
2265 tabexp.tails = array.ptrs;
2269 /* tab exp }}} */
2271 /* sort by name */
2272 struct command commands[] = {
2273 { "add", cmd_add, 1, 1, expand_add, 0, 0 },
2274 { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
2275 { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
2276 { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
2277 { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
2278 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
2279 { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
2280 { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
2281 { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
2282 { "fset", cmd_fset, 1, 1, NULL, 0, 0 },
2283 { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
2284 { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
2285 { "lqueue", cmd_lqueue, 0, 1, NULL, 0, 0 },
2286 { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
2287 { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
2288 { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
2289 { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
2290 { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
2291 { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
2292 { "quit", cmd_quit, 0, 0, NULL, 0, 0 },
2293 { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
2294 { "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
2295 { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
2296 { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
2297 { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
2298 { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
2299 { "set", cmd_set, 1, 1, expand_options, 0, 0 },
2300 { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
2301 { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
2302 { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
2303 { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
2304 { "tqueue", cmd_tqueue, 0, 1, NULL, 0, 0 },
2305 { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
2306 { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
2307 { "view", cmd_view, 1, 1, NULL, 0, 0 },
2308 { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
2309 { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
2310 { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
2311 { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
2312 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
2313 { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
2314 { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
2315 { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
2316 { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
2317 { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
2318 { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
2319 { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
2320 { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
2321 { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
2322 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
2323 { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
2324 { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
2325 { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
2326 { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
2327 { NULL, NULL, 0, 0, 0, 0, 0 }
2330 /* fills tabexp struct */
2331 static void expand_commands(const char *str)
2333 int i, len, pos;
2334 char **tails;
2336 /* tabexp is resetted */
2337 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2338 len = strlen(str);
2339 pos = 0;
2340 for (i = 0; commands[i].name; i++) {
2341 if (strncmp(str, commands[i].name, len) == 0)
2342 tails[pos++] = xstrdup(commands[i].name + len);
2344 if (pos > 0) {
2345 if (pos == 1) {
2346 /* only one command matches, add ' ' */
2347 char *tmp = xstrjoin(tails[0], " ");
2349 free(tails[0]);
2350 tails[0] = tmp;
2352 tails[pos] = NULL;
2353 tabexp.head = xstrdup(str);
2354 tabexp.tails = tails;
2355 } else {
2356 free(tails);
2360 struct command *get_command(const char *str)
2362 int i, len;
2364 while (*str == ' ')
2365 str++;
2366 for (len = 0; str[len] && str[len] != ' '; len++)
2369 for (i = 0; commands[i].name; i++) {
2370 if (strncmp(str, commands[i].name, len))
2371 continue;
2373 if (commands[i].name[len] == 0) {
2374 /* exact */
2375 return &commands[i];
2378 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2379 /* ambiguous */
2380 return NULL;
2382 return &commands[i];
2384 return NULL;
2387 /* fills tabexp struct */
2388 static void expand_command_line(const char *str)
2390 /* :command [arg]...
2392 * examples:
2394 * str expanded value (tabexp.head)
2395 * -------------------------------------
2396 * fs fset
2397 * b c bind common
2398 * se se (tabexp.tails = [ ek t ])
2400 /* command start/end, argument start */
2401 const char *cs, *ce, *as;
2402 const struct command *cmd;
2404 cs = str;
2405 ce = strchr(cs, ' ');
2406 if (ce == NULL) {
2407 /* expand command */
2408 expand_commands(cs);
2409 return;
2412 /* command must be expandable */
2413 cmd = get_command(cs);
2414 if (cmd == NULL) {
2415 /* command ambiguous or invalid */
2416 return;
2419 if (cmd->expand == NULL) {
2420 /* can't expand argument */
2421 return;
2424 as = ce;
2425 while (*as == ' ')
2426 as++;
2428 /* expand argument */
2429 cmd->expand(as);
2430 if (tabexp.head == NULL) {
2431 /* argument expansion failed */
2432 return;
2435 /* tabexp.head is now start of the argument string */
2436 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2437 free(tabexp.head);
2438 tabexp.head = xstrdup(expbuf);
2441 static void tab_expand(void)
2443 char *s1, *s2, *tmp;
2444 int pos;
2446 /* strip white space */
2447 pos = 0;
2448 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2449 pos++;
2451 /* string to expand */
2452 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2454 /* tail */
2455 s2 = xstrdup(cmdline.line + cmdline.bpos);
2457 tmp = tabexp_expand(s1, expand_command_line);
2458 if (tmp) {
2459 /* tmp.s2 */
2460 int l1, l2;
2462 l1 = strlen(tmp);
2463 l2 = strlen(s2);
2464 cmdline.blen = l1 + l2;
2465 if (cmdline.blen >= cmdline.size) {
2466 while (cmdline.blen >= cmdline.size)
2467 cmdline.size *= 2;
2468 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2470 sprintf(cmdline.line, "%s%s", tmp, s2);
2471 cmdline.bpos = l1;
2472 cmdline.cpos = u_strlen(tmp);
2473 cmdline.clen = u_strlen(cmdline.line);
2474 free(tmp);
2476 free(s1);
2477 free(s2);
2480 static void reset_tab_expansion(void)
2482 tabexp_reset();
2483 arg_expand_cmd = -1;
2486 int run_only_safe_commands;
2488 /* FIXME: parse all arguments */
2489 void run_command(const char *buf)
2491 char *cmd, *arg;
2492 int cmd_start, cmd_end, cmd_len;
2493 int arg_start, arg_end;
2494 int i;
2496 i = 0;
2497 while (buf[i] && buf[i] == ' ')
2498 i++;
2500 if (buf[i] == '#')
2501 return;
2503 cmd_start = i;
2504 while (buf[i] && buf[i] != ' ')
2505 i++;
2506 cmd_end = i;
2507 while (buf[i] && buf[i] == ' ')
2508 i++;
2509 arg_start = i;
2510 while (buf[i])
2511 i++;
2512 arg_end = i;
2514 cmd_len = cmd_end - cmd_start;
2515 if (cmd_len == 0)
2516 return;
2518 cmd = xstrndup(buf + cmd_start, cmd_len);
2519 if (arg_start == arg_end) {
2520 arg = NULL;
2521 } else {
2522 arg = xstrndup(buf + arg_start, arg_end - arg_start);
2524 i = 0;
2525 while (1) {
2526 const struct command *c = &commands[i];
2528 if (c->name == NULL) {
2529 error_msg("unknown command\n");
2530 break;
2532 if (strncmp(cmd, c->name, cmd_len) == 0) {
2533 const char *next = commands[i + 1].name;
2534 int exact = c->name[cmd_len] == 0;
2536 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2537 error_msg("ambiguous command\n");
2538 break;
2540 if (c->min_args > 0 && arg == NULL) {
2541 error_msg("not enough arguments\n");
2542 break;
2544 if (c->max_args == 0 && arg) {
2545 error_msg("too many arguments\n");
2546 break;
2548 if (run_only_safe_commands && c->flags & CMD_UNSAFE) {
2549 d_print("trying to execute unsafe command over net\n");
2550 break;
2552 c->func(arg);
2553 break;
2555 i++;
2557 free(arg);
2558 free(cmd);
2561 static void reset_history_search(void)
2563 history_reset_search(&cmd_history);
2564 free(history_search_text);
2565 history_search_text = NULL;
2568 static void backspace(void)
2570 if (cmdline.clen > 0) {
2571 cmdline_backspace();
2572 } else {
2573 input_mode = NORMAL_MODE;
2577 void command_mode_ch(uchar ch)
2579 switch (ch) {
2580 case 0x01: // ^A
2581 cmdline_move_home();
2582 break;
2583 case 0x02: // ^B
2584 cmdline_move_left();
2585 break;
2586 case 0x04: // ^D
2587 cmdline_delete_ch();
2588 break;
2589 case 0x05: // ^E
2590 cmdline_move_end();
2591 break;
2592 case 0x06: // ^F
2593 cmdline_move_right();
2594 break;
2595 case 0x03: // ^C
2596 case 0x07: // ^G
2597 case 0x1B: // ESC
2598 if (cmdline.blen) {
2599 history_add_line(&cmd_history, cmdline.line);
2600 cmdline_clear();
2602 input_mode = NORMAL_MODE;
2603 break;
2604 case 0x0A:
2605 if (cmdline.blen) {
2606 run_command(cmdline.line);
2607 history_add_line(&cmd_history, cmdline.line);
2608 cmdline_clear();
2610 input_mode = NORMAL_MODE;
2611 break;
2612 case 0x0B:
2613 cmdline_clear_end();
2614 break;
2615 case 0x09:
2616 /* tab expansion should not complain */
2617 display_errors = 0;
2619 tab_expand();
2620 break;
2621 case 0x15:
2622 cmdline_backspace_to_bol();
2623 break;
2624 case 127:
2625 backspace();
2626 break;
2627 default:
2628 cmdline_insert_ch(ch);
2630 reset_history_search();
2631 if (ch != 0x09)
2632 reset_tab_expansion();
2635 void command_mode_key(int key)
2637 reset_tab_expansion();
2638 switch (key) {
2639 case KEY_DC:
2640 cmdline_delete_ch();
2641 break;
2642 case KEY_BACKSPACE:
2643 backspace();
2644 break;
2645 case KEY_LEFT:
2646 cmdline_move_left();
2647 return;
2648 case KEY_RIGHT:
2649 cmdline_move_right();
2650 return;
2651 case KEY_HOME:
2652 cmdline_move_home();
2653 return;
2654 case KEY_END:
2655 cmdline_move_end();
2656 return;
2657 case KEY_UP:
2659 const char *s;
2661 if (history_search_text == NULL)
2662 history_search_text = xstrdup(cmdline.line);
2663 s = history_search_forward(&cmd_history, history_search_text);
2664 if (s)
2665 cmdline_set_text(s);
2667 return;
2668 case KEY_DOWN:
2669 if (history_search_text) {
2670 const char *s;
2672 s = history_search_backward(&cmd_history, history_search_text);
2673 if (s) {
2674 cmdline_set_text(s);
2675 } else {
2676 cmdline_set_text(history_search_text);
2679 return;
2680 default:
2681 d_print("key = %c (%d)\n", key, key);
2683 reset_history_search();
2686 void commands_init(void)
2688 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2689 history_load(&cmd_history, cmd_history_filename, 2000);
2692 void commands_exit(void)
2694 history_save(&cmd_history);
2695 free(cmd_history_filename);
2696 tabexp_reset();