seek: Ceil position to duration - 5s
[cmus.git] / command_mode.c
blob2f2acdb52593bd996f8e93d6ce30d23b834a7c17
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "command_mode.h"
21 #include "search_mode.h"
22 #include "cmdline.h"
23 #include "options.h"
24 #include "ui_curses.h"
25 #include "history.h"
26 #include "tabexp.h"
27 #include "tabexp_file.h"
28 #include "browser.h"
29 #include "filters.h"
30 #include "player.h"
31 #include "editable.h"
32 #include "lib.h"
33 #include "pl.h"
34 #include "play_queue.h"
35 #include "cmus.h"
36 #include "worker.h"
37 #include "keys.h"
38 #include "xmalloc.h"
39 #include "xstrjoin.h"
40 #include "misc.h"
41 #include "path.h"
42 #include "format_print.h"
43 #include "spawn.h"
44 #include "utils.h"
45 #include "list.h"
46 #include "debug.h"
47 #include "load_dir.h"
48 #include "config/datadir.h"
49 #include "help.h"
51 #include <stdlib.h>
52 #include <ctype.h>
53 #include <sys/types.h>
54 #include <sys/wait.h>
55 #include <dirent.h>
56 #include <pwd.h>
58 #if defined(__sun__)
59 #include <ncurses.h>
60 #else
61 #include <curses.h>
62 #endif
64 static struct history cmd_history;
65 static char *cmd_history_filename;
66 static char *history_search_text = NULL;
67 static int arg_expand_cmd = -1;
68 static int prev_view = -1;
70 static char *get_home_dir(const char *username)
72 struct passwd *passwd;
74 if (username == NULL)
75 return xstrdup(home_dir);
76 passwd = getpwnam(username);
77 if (passwd == NULL)
78 return NULL;
79 /* don't free passwd */
80 return xstrdup(passwd->pw_dir);
83 static char *expand_filename(const char *name)
85 if (name[0] == '~') {
86 char *slash;
88 slash = strchr(name, '/');
89 if (slash) {
90 char *username, *home;
92 if (slash - name - 1 > 0) {
93 /* ~user/... */
94 username = xstrndup(name + 1, slash - name - 1);
95 } else {
96 /* ~/... */
97 username = NULL;
99 home = get_home_dir(username);
100 free(username);
101 if (home) {
102 char *expanded;
104 expanded = xstrjoin(home, slash);
105 free(home);
106 return expanded;
107 } else {
108 return xstrdup(name);
110 } else {
111 if (name[1] == 0) {
112 return xstrdup(home_dir);
113 } else {
114 char *home;
116 home = get_home_dir(name + 1);
117 if (home)
118 return home;
119 return xstrdup(name);
122 } else {
123 return xstrdup(name);
127 /* view {{{ */
129 void view_clear(int view)
131 switch (view) {
132 case TREE_VIEW:
133 case SORTED_VIEW:
134 worker_remove_jobs(JOB_TYPE_LIB);
135 editable_lock();
136 editable_clear(&lib_editable);
138 /* FIXME: make this optional? */
139 lib_clear_store();
141 editable_unlock();
142 break;
143 case PLAYLIST_VIEW:
144 worker_remove_jobs(JOB_TYPE_PL);
145 editable_lock();
146 editable_clear(&pl_editable);
147 editable_unlock();
148 break;
149 case QUEUE_VIEW:
150 worker_remove_jobs(JOB_TYPE_QUEUE);
151 editable_lock();
152 editable_clear(&pq_editable);
153 editable_unlock();
154 break;
155 default:
156 info_msg(":clear only works in views 1-4");
160 void view_add(int view, char *arg, int prepend)
162 char *tmp, *name;
163 enum file_type ft;
165 tmp = expand_filename(arg);
166 ft = cmus_detect_ft(tmp, &name);
167 if (ft == FILE_TYPE_INVALID) {
168 error_msg("adding '%s': %s", tmp, strerror(errno));
169 free(tmp);
170 return;
172 free(tmp);
174 switch (view) {
175 case TREE_VIEW:
176 case SORTED_VIEW:
177 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
178 break;
179 case PLAYLIST_VIEW:
180 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
181 break;
182 case QUEUE_VIEW:
183 if (prepend) {
184 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
185 } else {
186 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
188 break;
189 default:
190 info_msg(":add only works in views 1-4");
192 free(name);
195 void view_load(int view, char *arg)
197 char *tmp, *name;
198 enum file_type ft;
200 tmp = expand_filename(arg);
201 ft = cmus_detect_ft(tmp, &name);
202 if (ft == FILE_TYPE_INVALID) {
203 error_msg("loading '%s': %s", tmp, strerror(errno));
204 free(tmp);
205 return;
207 free(tmp);
209 if (ft == FILE_TYPE_FILE)
210 ft = FILE_TYPE_PL;
211 if (ft != FILE_TYPE_PL) {
212 error_msg("loading '%s': not a playlist file", name);
213 free(name);
214 return;
217 switch (view) {
218 case TREE_VIEW:
219 case SORTED_VIEW:
220 worker_remove_jobs(JOB_TYPE_LIB);
221 editable_lock();
222 editable_clear(&lib_editable);
223 editable_unlock();
224 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
225 free(lib_filename);
226 lib_filename = name;
227 break;
228 case PLAYLIST_VIEW:
229 worker_remove_jobs(JOB_TYPE_PL);
230 editable_lock();
231 editable_clear(&pl_editable);
232 editable_unlock();
233 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
234 free(pl_filename);
235 pl_filename = name;
236 break;
237 default:
238 info_msg(":load only works in views 1-3");
239 free(name);
243 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
245 char *filename = *filenamep;
247 if (arg) {
248 free(filename);
249 filename = xstrdup(arg);
250 *filenamep = filename;
253 editable_lock();
254 if (cmus_save(for_each_ti, filename) == -1)
255 error_msg("saving '%s': %s", filename, strerror(errno));
256 editable_unlock();
259 void view_save(int view, char *arg)
261 if (arg) {
262 char *tmp;
264 tmp = expand_filename(arg);
265 arg = path_absolute(tmp);
266 free(tmp);
269 switch (view) {
270 case TREE_VIEW:
271 case SORTED_VIEW:
272 do_save(lib_for_each, arg, &lib_filename);
273 break;
274 case PLAYLIST_VIEW:
275 do_save(pl_for_each, arg, &pl_filename);
276 break;
277 default:
278 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
280 free(arg);
283 /* }}} */
285 /* only returns the last flag which is enough for it's callers */
286 static int parse_flags(const char **strp, const char *flags)
288 const char *str = *strp;
289 int flag = 0;
291 if (str == NULL)
292 return flag;
294 while (*str) {
295 if (*str != '-')
296 break;
298 // "-"
299 if (str[1] == 0)
300 break;
302 // "--" or "-- "
303 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
304 str += 2;
305 break;
308 // not "-?" or "-? "
309 if (str[2] && str[2] != ' ')
310 break;
312 flag = str[1];
313 if (!strchr(flags, flag)) {
314 error_msg("invalid option -%c", flag);
315 return -1;
318 str += 2;
320 while (*str == ' ')
321 str++;
323 while (*str == ' ')
324 str++;
325 if (*str == 0)
326 str = NULL;
327 *strp = str;
328 return flag;
331 static int flag_to_view(int flag)
333 switch (flag) {
334 case 'l':
335 return TREE_VIEW;
336 case 'p':
337 return PLAYLIST_VIEW;
338 case 'q':
339 case 'Q':
340 return QUEUE_VIEW;
341 default:
342 return cur_view;
346 static void cmd_add(char *arg)
348 int flag = parse_flags((const char **)&arg, "lpqQ");
350 if (flag == -1)
351 return;
352 if (arg == NULL) {
353 error_msg("not enough arguments\n");
354 return;
356 view_add(flag_to_view(flag), arg, flag == 'Q');
359 static void cmd_clear(char *arg)
361 int flag = parse_flags((const char **)&arg, "lpq");
363 if (flag == -1)
364 return;
365 if (arg) {
366 error_msg("too many arguments\n");
367 return;
369 view_clear(flag_to_view(flag));
372 static void cmd_load(char *arg)
374 int flag = parse_flags((const char **)&arg, "lp");
376 if (flag == -1)
377 return;
378 if (arg == NULL) {
379 error_msg("not enough arguments\n");
380 return;
382 view_load(flag_to_view(flag), arg);
385 static void cmd_save(char *arg)
387 int flag = parse_flags((const char **)&arg, "lp");
389 if (flag == -1)
390 return;
391 view_save(flag_to_view(flag), arg);
394 static void cmd_set(char *arg)
396 char *value = NULL;
397 int i;
399 for (i = 0; arg[i]; i++) {
400 if (arg[i] == '=') {
401 arg[i] = 0;
402 value = &arg[i + 1];
403 break;
406 if (value) {
407 option_set(arg, value);
408 help_win->changed = 1;
409 } else {
410 struct cmus_opt *opt;
411 char buf[OPTION_MAX_SIZE];
413 /* support "set <option>?" */
414 i--;
415 if (arg[i] == '?')
416 arg[i] = 0;
418 opt = option_find(arg);
419 if (opt) {
420 opt->get(opt->id, buf);
421 info_msg("setting: '%s=%s'", arg, buf);
426 static void cmd_toggle(char *arg)
428 struct cmus_opt *opt = option_find(arg);
430 if (opt == NULL)
431 return;
433 if (opt->toggle == NULL) {
434 error_msg("%s is not toggle option", opt->name);
435 return;
437 opt->toggle(opt->id);
438 help_win->changed = 1;
441 static int get_number(char *str, char **end)
443 int val = 0;
445 while (*str >= '0' && *str <= '9') {
446 val *= 10;
447 val += *str++ - '0';
449 *end = str;
450 return val;
453 static void cmd_seek(char *arg)
455 int relative = 0;
456 int seek = 0, sign = 1, count;
458 switch (*arg) {
459 case '-':
460 sign = -1;
461 case '+':
462 relative = 1;
463 arg++;
464 break;
467 count = 0;
468 goto inside;
470 do {
471 int num;
472 char *end;
474 if (*arg != ':')
475 break;
476 arg++;
477 inside:
478 num = get_number(arg, &end);
479 if (arg == end)
480 break;
481 arg = end;
482 seek = seek * 60 + num;
483 } while (++count < 3);
485 seek *= sign;
486 if (!count)
487 goto err;
489 if (count == 1) {
490 switch (tolower(*arg)) {
491 case 'h':
492 seek *= 60;
493 case 'm':
494 seek *= 60;
495 case 's':
496 arg++;
497 break;
501 if (!*arg) {
502 player_seek(seek, relative);
503 return;
505 err:
506 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
509 static void cmd_factivate(char *arg)
511 editable_lock();
512 filters_activate_names(arg);
513 editable_unlock();
516 static void cmd_filter(char *arg)
518 editable_lock();
519 filters_set_anonymous(arg);
520 editable_unlock();
523 static void cmd_fset(char *arg)
525 filters_set_filter(arg);
528 static void cmd_invert(char *arg)
530 editable_lock();
531 switch (cur_view) {
532 case SORTED_VIEW:
533 editable_invert_marks(&lib_editable);
534 break;
535 case PLAYLIST_VIEW:
536 editable_invert_marks(&pl_editable);
537 break;
538 case QUEUE_VIEW:
539 editable_invert_marks(&pq_editable);
540 break;
541 default:
542 info_msg(":invert only works in views 2-4");
544 editable_unlock();
547 static void cmd_mark(char *arg)
549 editable_lock();
550 switch (cur_view) {
551 case SORTED_VIEW:
552 editable_mark(&lib_editable, arg);
553 break;
554 case PLAYLIST_VIEW:
555 editable_mark(&pl_editable, arg);
556 break;
557 case QUEUE_VIEW:
558 editable_mark(&pq_editable, arg);
559 break;
560 default:
561 info_msg(":mark only works in views 2-4");
563 editable_unlock();
566 static void cmd_unmark(char *arg)
568 editable_lock();
569 switch (cur_view) {
570 case SORTED_VIEW:
571 editable_unmark(&lib_editable);
572 break;
573 case PLAYLIST_VIEW:
574 editable_unmark(&pl_editable);
575 break;
576 case QUEUE_VIEW:
577 editable_unmark(&pq_editable);
578 break;
579 default:
580 info_msg(":unmark only works in views 2-4");
582 editable_unlock();
585 static void cmd_cd(char *arg)
587 if (arg) {
588 char *dir, *absolute;
590 dir = expand_filename(arg);
591 absolute = path_absolute(dir);
592 if (chdir(dir) == -1) {
593 error_msg("could not cd to '%s': %s", dir, strerror(errno));
594 } else {
595 browser_chdir(absolute);
597 free(absolute);
598 free(dir);
599 } else {
600 if (chdir(home_dir) == -1) {
601 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
602 } else {
603 browser_chdir(home_dir);
608 static void cmd_bind(char *arg)
610 int flag = parse_flags((const char **)&arg, "f");
611 char *key, *func;
613 if (flag == -1)
614 return;
616 if (arg == NULL)
617 goto err;
619 key = strchr(arg, ' ');
620 if (key == NULL)
621 goto err;
622 *key++ = 0;
623 while (*key == ' ')
624 key++;
626 func = strchr(key, ' ');
627 if (func == NULL)
628 goto err;
629 *func++ = 0;
630 while (*func == ' ')
631 func++;
632 if (*func == 0)
633 goto err;
635 key_bind(arg, key, func, flag == 'f');
636 return;
637 err:
638 error_msg("expecting 3 arguments (context, key and function)\n");
641 static void cmd_unbind(char *arg)
643 int flag = parse_flags((const char **)&arg, "f");
644 char *key;
646 if (flag == -1)
647 return;
649 if (arg == NULL)
650 goto err;
652 key = strchr(arg, ' ');
653 if (key == NULL)
654 goto err;
655 *key++ = 0;
656 while (*key == ' ')
657 key++;
658 if (*key == 0)
659 goto err;
661 /* FIXME: remove spaces at end */
663 key_unbind(arg, key, flag == 'f');
664 return;
665 err:
666 error_msg("expecting 2 arguments (context and key)\n");
669 static void cmd_showbind(char *arg)
671 char *key;
673 key = strchr(arg, ' ');
674 if (key == NULL)
675 goto err;
676 *key++ = 0;
677 while (*key == ' ')
678 key++;
679 if (*key == 0)
680 goto err;
682 /* FIXME: remove spaces at end */
684 show_binding(arg, key);
685 return;
686 err:
687 error_msg("expecting 2 arguments (context and key)\n");
690 static void cmd_quit(char *arg)
692 quit();
695 static void cmd_reshuffle(char *arg)
697 editable_lock();
698 lib_reshuffle();
699 pl_reshuffle();
700 editable_unlock();
703 static void cmd_source(char *arg)
705 char *filename = expand_filename(arg);
707 if (source_file(filename) == -1)
708 error_msg("sourcing %s: %s", filename, strerror(errno));
709 free(filename);
712 static void cmd_colorscheme(char *arg)
714 char filename[512];
716 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
717 if (source_file(filename) == -1) {
718 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
719 if (source_file(filename) == -1)
720 error_msg("sourcing %s: %s", filename, strerror(errno));
725 * \" inside double-quotes becomes "
726 * \\ inside double-quotes becomes \
728 static char *parse_quoted(const char **strp)
730 const char *str = *strp;
731 const char *start;
732 char *ret, *dst;
734 str++;
735 start = str;
736 while (1) {
737 int c = *str++;
739 if (c == 0)
740 goto error;
741 if (c == '"')
742 break;
743 if (c == '\\') {
744 if (*str++ == 0)
745 goto error;
748 *strp = str;
749 ret = xnew(char, str - start);
750 str = start;
751 dst = ret;
752 while (1) {
753 int c = *str++;
755 if (c == '"')
756 break;
757 if (c == '\\') {
758 c = *str++;
759 if (c != '"' && c != '\\')
760 *dst++ = '\\';
762 *dst++ = c;
764 *dst = 0;
765 return ret;
766 error:
767 error_msg("`\"' expected");
768 return NULL;
771 static char *parse_escaped(const char **strp)
773 const char *str = *strp;
774 const char *start;
775 char *ret, *dst;
777 start = str;
778 while (1) {
779 int c = *str;
781 if (c == 0 || c == ' ' || c == '\'' || c == '"')
782 break;
784 str++;
785 if (c == '\\') {
786 c = *str;
787 if (c == 0)
788 break;
789 str++;
792 *strp = str;
793 ret = xnew(char, str - start + 1);
794 str = start;
795 dst = ret;
796 while (1) {
797 int c = *str;
799 if (c == 0 || c == ' ' || c == '\'' || c == '"')
800 break;
802 str++;
803 if (c == '\\') {
804 c = *str;
805 if (c == 0) {
806 *dst++ = '\\';
807 break;
809 str++;
811 *dst++ = c;
813 *dst = 0;
814 return ret;
817 static char *parse_one(const char **strp)
819 const char *str = *strp;
820 char *ret = NULL;
822 while (1) {
823 char *part;
824 int c = *str;
826 if (!c || c == ' ')
827 break;
828 if (c == '"') {
829 part = parse_quoted(&str);
830 if (part == NULL)
831 goto error;
832 } else if (c == '\'') {
833 /* backslashes are normal chars inside single-quotes */
834 const char *end;
836 str++;
837 end = strchr(str, '\'');
838 if (end == NULL)
839 goto sq_missing;
840 part = xstrndup(str, end - str);
841 str = end + 1;
842 } else {
843 part = parse_escaped(&str);
846 if (ret == NULL) {
847 ret = part;
848 } else {
849 char *tmp = xstrjoin(ret, part);
850 free(ret);
851 ret = tmp;
854 *strp = str;
855 return ret;
856 sq_missing:
857 error_msg("`'' expected");
858 error:
859 free(ret);
860 return NULL;
863 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
865 char **av = NULL;
866 int nr = 0;
867 int alloc = 0;
869 while (*cmd) {
870 char *arg;
872 /* there can't be spaces at start of command
873 * and there is at least one argument */
874 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
875 /* {} is replaced with file arguments */
876 if (*args_idx != -1)
877 goto only_once_please;
878 *args_idx = nr;
879 cmd += 2;
880 goto skip_spaces;
881 } else {
882 arg = parse_one(&cmd);
883 if (arg == NULL)
884 goto error;
887 if (nr == alloc) {
888 alloc = alloc ? alloc * 2 : 4;
889 av = xrenew(char *, av, alloc + 1);
891 av[nr++] = arg;
892 skip_spaces:
893 while (*cmd == ' ')
894 cmd++;
896 av[nr] = NULL;
897 *ac = nr;
898 return av;
899 only_once_please:
900 error_msg("{} can be used only once");
901 error:
902 while (nr > 0)
903 free(av[--nr]);
904 free(av);
905 return NULL;
908 static struct track_info **sel_tis;
909 static int sel_tis_alloc;
910 static int sel_tis_nr;
912 static int add_ti(void *data, struct track_info *ti)
914 if (sel_tis_nr == sel_tis_alloc) {
915 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
916 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
918 track_info_ref(ti);
919 sel_tis[sel_tis_nr++] = ti;
920 return 0;
923 static void cmd_run(char *arg)
925 char **av, **argv;
926 int ac, argc, i, run, files_idx = -1;
928 if (cur_view > QUEUE_VIEW) {
929 info_msg("Command execution is supported only in views 1-4");
930 return;
933 av = parse_cmd(arg, &files_idx, &ac);
934 if (av == NULL) {
935 return;
938 /* collect selected files (struct track_info) */
939 sel_tis = NULL;
940 sel_tis_alloc = 0;
941 sel_tis_nr = 0;
943 editable_lock();
944 switch (cur_view) {
945 case TREE_VIEW:
946 __tree_for_each_sel(add_ti, NULL, 0);
947 break;
948 case SORTED_VIEW:
949 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
950 break;
951 case PLAYLIST_VIEW:
952 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
953 break;
954 case QUEUE_VIEW:
955 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
956 break;
958 editable_unlock();
960 if (sel_tis_nr == 0) {
961 /* no files selected, do nothing */
962 free_str_array(av);
963 return;
965 sel_tis[sel_tis_nr] = NULL;
967 /* build argv */
968 argv = xnew(char *, ac + sel_tis_nr + 1);
969 argc = 0;
970 if (files_idx == -1) {
971 /* add selected files after rest of the args */
972 for (i = 0; i < ac; i++)
973 argv[argc++] = av[i];
974 for (i = 0; i < sel_tis_nr; i++)
975 argv[argc++] = sel_tis[i]->filename;
976 } else {
977 for (i = 0; i < files_idx; i++)
978 argv[argc++] = av[i];
979 for (i = 0; i < sel_tis_nr; i++)
980 argv[argc++] = sel_tis[i]->filename;
981 for (i = files_idx; i < ac; i++)
982 argv[argc++] = av[i];
984 argv[argc] = NULL;
986 for (i = 0; argv[i]; i++)
987 d_print("ARG: '%s'\n", argv[i]);
989 run = 1;
990 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
991 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
992 info_msg("Aborted");
993 run = 0;
996 if (run) {
997 int status;
999 if (spawn(argv, &status)) {
1000 error_msg("executing %s: %s", argv[0], strerror(errno));
1001 } else {
1002 if (WIFEXITED(status)) {
1003 int rc = WEXITSTATUS(status);
1005 if (rc)
1006 error_msg("%s returned %d", argv[0], rc);
1008 if (WIFSIGNALED(status))
1009 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1011 switch (cur_view) {
1012 case TREE_VIEW:
1013 case SORTED_VIEW:
1014 /* this must be done before sel_tis are unreffed */
1015 free_str_array(av);
1016 free(argv);
1018 /* remove non-existed files, update tags for changed files */
1019 cmus_update_tis(sel_tis, sel_tis_nr);
1021 /* we don't own sel_tis anymore! */
1022 return;
1026 free_str_array(av);
1027 free(argv);
1028 for (i = 0; sel_tis[i]; i++)
1029 track_info_unref(sel_tis[i]);
1030 free(sel_tis);
1033 static int get_one_ti(void *data, struct track_info *ti)
1035 struct track_info **sel_ti = data;
1037 track_info_ref(ti);
1038 *sel_ti = ti;
1039 /* stop the for each loop, we need only the first selected track */
1040 return 1;
1043 static void cmd_echo(char *arg)
1045 struct track_info *sel_ti;
1046 char *ptr = arg;
1048 while (1) {
1049 ptr = strchr(ptr, '{');
1050 if (ptr == NULL)
1051 break;
1052 if (ptr[1] == '}')
1053 break;
1054 ptr++;
1057 if (ptr == NULL) {
1058 info_msg("%s", arg);
1059 return;
1062 if (cur_view > QUEUE_VIEW) {
1063 info_msg("echo with {} in its arguments is supported only in views 1-4");
1064 return;
1067 *ptr = 0;
1068 ptr += 2;
1070 /* get only the first selected track */
1071 sel_ti = NULL;
1073 editable_lock();
1074 switch (cur_view) {
1075 case TREE_VIEW:
1076 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1077 break;
1078 case SORTED_VIEW:
1079 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1080 break;
1081 case PLAYLIST_VIEW:
1082 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1083 break;
1084 case QUEUE_VIEW:
1085 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1086 break;
1088 editable_unlock();
1090 if (sel_ti == NULL)
1091 return;
1093 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1094 track_info_unref(sel_ti);
1097 #define VF_RELATIVE 0x01
1098 #define VF_PERCENTAGE 0x02
1100 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1102 unsigned int f = 0;
1103 int ch, val = 0, digits = 0, sign = 1;
1105 if (*arg == '-') {
1106 arg++;
1107 f |= VF_RELATIVE;
1108 sign = -1;
1109 } else if (*arg == '+') {
1110 arg++;
1111 f |= VF_RELATIVE;
1114 while (1) {
1115 ch = *arg++;
1116 if (ch < '0' || ch > '9')
1117 break;
1118 val *= 10;
1119 val += ch - '0';
1120 digits++;
1122 if (digits == 0)
1123 goto err;
1125 if (ch == '%') {
1126 f |= VF_PERCENTAGE;
1127 ch = *arg;
1129 if (ch)
1130 goto err;
1132 *value = sign * val;
1133 *flags = f;
1134 return 0;
1135 err:
1136 return -1;
1139 static int calc_vol(int val, int old, unsigned int flags)
1141 if (flags & VF_RELATIVE) {
1142 if (flags & VF_PERCENTAGE)
1143 val = scale_from_percentage(val, volume_max);
1144 val += old;
1145 } else if (flags & VF_PERCENTAGE) {
1146 val = scale_from_percentage(val, volume_max);
1148 return clamp(val, 0, volume_max);
1152 * :vol value [value]
1154 * where value is [-+]?[0-9]+%?
1156 static void cmd_vol(char *arg)
1158 char **values = get_words(arg);
1159 unsigned int lf, rf;
1160 int l, r, ol, or;
1162 if (values[1] && values[2])
1163 goto err;
1165 if (parse_vol_arg(values[0], &l, &lf))
1166 goto err;
1168 r = l;
1169 rf = lf;
1170 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1171 goto err;
1173 free_str_array(values);
1175 player_get_volume(&ol, &or);
1176 l = calc_vol(l, ol, lf);
1177 r = calc_vol(r, or, rf);
1178 player_set_volume(l, r);
1179 return;
1180 err:
1181 free_str_array(values);
1182 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1185 static void cmd_prev_view(char *arg)
1187 int tmp;
1188 if (prev_view >= 0) {
1189 tmp = cur_view;
1190 set_view(prev_view);
1191 prev_view = tmp;
1195 static void cmd_view(char *arg)
1197 int view;
1199 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view) && (view - 1) != cur_view) {
1200 prev_view = cur_view;
1201 set_view(view - 1);
1205 static void cmd_p_next(char *arg)
1207 cmus_next();
1210 static void cmd_p_pause(char *arg)
1212 player_pause();
1215 static void cmd_p_play(char *arg)
1217 if (arg) {
1218 cmus_play_file(arg);
1219 } else {
1220 player_play();
1224 static void cmd_p_prev(char *arg)
1226 cmus_prev();
1229 static void cmd_p_stop(char *arg)
1231 player_stop();
1234 static void cmd_search_next(char *arg)
1236 if (search_str) {
1237 if (!search_next(searchable, search_str, search_direction))
1238 search_not_found();
1242 static void cmd_search_prev(char *arg)
1244 if (search_str) {
1245 if (!search_next(searchable, search_str, !search_direction))
1246 search_not_found();
1250 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1252 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1255 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1257 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1260 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1262 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1265 static for_each_sel_ti_cb view_for_each_sel[4] = {
1266 tree_for_each_sel,
1267 sorted_for_each_sel,
1268 pl_for_each_sel,
1269 pq_for_each_sel
1272 /* wrapper for void lib_add_track(struct track_info *) etc. */
1273 static int wrapper_cb(void *data, struct track_info *ti)
1275 add_ti_cb add = data;
1277 add(ti);
1278 return 0;
1281 static void add_from_browser(add_ti_cb add, int job_type)
1283 char *sel = browser_get_sel();
1285 if (sel) {
1286 enum file_type ft;
1287 char *ret;
1289 ft = cmus_detect_ft(sel, &ret);
1290 if (ft != FILE_TYPE_INVALID) {
1291 cmus_add(add, ret, ft, job_type);
1292 window_down(browser_win, 1);
1294 free(ret);
1295 free(sel);
1299 static void cmd_win_add_l(char *arg)
1301 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1302 return;
1304 if (cur_view <= QUEUE_VIEW) {
1305 editable_lock();
1306 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1307 editable_unlock();
1308 } else if (cur_view == BROWSER_VIEW) {
1309 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1313 static void cmd_win_add_p(char *arg)
1315 /* could allow adding dups? */
1316 if (cur_view == PLAYLIST_VIEW)
1317 return;
1319 if (cur_view <= QUEUE_VIEW) {
1320 editable_lock();
1321 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1322 editable_unlock();
1323 } else if (cur_view == BROWSER_VIEW) {
1324 add_from_browser(pl_add_track, JOB_TYPE_PL);
1328 static void cmd_win_add_Q(char *arg)
1330 if (cur_view == QUEUE_VIEW)
1331 return;
1333 if (cur_view <= QUEUE_VIEW) {
1334 editable_lock();
1335 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1336 editable_unlock();
1337 } else if (cur_view == BROWSER_VIEW) {
1338 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1342 static void cmd_win_add_q(char *arg)
1344 if (cur_view == QUEUE_VIEW)
1345 return;
1347 if (cur_view <= QUEUE_VIEW) {
1348 editable_lock();
1349 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1350 editable_unlock();
1351 } else if (cur_view == BROWSER_VIEW) {
1352 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1356 static void cmd_win_activate(char *arg)
1358 struct track_info *info = NULL;
1360 editable_lock();
1361 switch (cur_view) {
1362 case TREE_VIEW:
1363 info = tree_set_selected();
1364 break;
1365 case SORTED_VIEW:
1366 info = sorted_set_selected();
1367 break;
1368 case PLAYLIST_VIEW:
1369 info = pl_set_selected();
1370 break;
1371 case QUEUE_VIEW:
1372 break;
1373 case BROWSER_VIEW:
1374 browser_enter();
1375 break;
1376 case FILTERS_VIEW:
1377 filters_activate();
1378 break;
1379 case HELP_VIEW:
1380 help_select();
1381 break;
1383 editable_unlock();
1385 if (info) {
1386 /* update lib/pl mode */
1387 if (cur_view < 2)
1388 play_library = 1;
1389 if (cur_view == 2)
1390 play_library = 0;
1392 player_play_file(info);
1396 static void cmd_win_mv_after(char *arg)
1398 editable_lock();
1399 switch (cur_view) {
1400 case TREE_VIEW:
1401 break;
1402 case SORTED_VIEW:
1403 editable_move_after(&lib_editable);
1404 break;
1405 case PLAYLIST_VIEW:
1406 editable_move_after(&pl_editable);
1407 break;
1408 case QUEUE_VIEW:
1409 editable_move_after(&pq_editable);
1410 break;
1411 case BROWSER_VIEW:
1412 break;
1413 case FILTERS_VIEW:
1414 break;
1415 case HELP_VIEW:
1416 break;
1418 editable_unlock();
1421 static void cmd_win_mv_before(char *arg)
1423 editable_lock();
1424 switch (cur_view) {
1425 case TREE_VIEW:
1426 break;
1427 case SORTED_VIEW:
1428 editable_move_before(&lib_editable);
1429 break;
1430 case PLAYLIST_VIEW:
1431 editable_move_before(&pl_editable);
1432 break;
1433 case QUEUE_VIEW:
1434 editable_move_before(&pq_editable);
1435 break;
1436 case BROWSER_VIEW:
1437 break;
1438 case FILTERS_VIEW:
1439 break;
1440 case HELP_VIEW:
1441 break;
1443 editable_unlock();
1446 static void cmd_win_remove(char *arg)
1448 editable_lock();
1449 switch (cur_view) {
1450 case TREE_VIEW:
1451 tree_remove_sel();
1452 break;
1453 case SORTED_VIEW:
1454 editable_remove_sel(&lib_editable);
1455 break;
1456 case PLAYLIST_VIEW:
1457 editable_remove_sel(&pl_editable);
1458 break;
1459 case QUEUE_VIEW:
1460 editable_remove_sel(&pq_editable);
1461 break;
1462 case BROWSER_VIEW:
1463 browser_delete();
1464 break;
1465 case FILTERS_VIEW:
1466 filters_delete_filter();
1467 break;
1468 case HELP_VIEW:
1469 help_remove();
1470 break;
1472 editable_unlock();
1475 static void cmd_win_sel_cur(char *arg)
1477 editable_lock();
1478 switch (cur_view) {
1479 case TREE_VIEW:
1480 tree_sel_current();
1481 break;
1482 case SORTED_VIEW:
1483 sorted_sel_current();
1484 break;
1485 case PLAYLIST_VIEW:
1486 pl_sel_current();
1487 break;
1488 case QUEUE_VIEW:
1489 break;
1490 case BROWSER_VIEW:
1491 break;
1492 case FILTERS_VIEW:
1493 break;
1494 case HELP_VIEW:
1495 break;
1497 editable_unlock();
1500 static void cmd_win_toggle(char *arg)
1502 switch (cur_view) {
1503 case TREE_VIEW:
1504 editable_lock();
1505 tree_toggle_expand_artist();
1506 editable_unlock();
1507 break;
1508 case SORTED_VIEW:
1509 editable_lock();
1510 editable_toggle_mark(&lib_editable);
1511 editable_unlock();
1512 break;
1513 case PLAYLIST_VIEW:
1514 editable_lock();
1515 editable_toggle_mark(&pl_editable);
1516 editable_unlock();
1517 break;
1518 case QUEUE_VIEW:
1519 editable_lock();
1520 editable_toggle_mark(&pq_editable);
1521 editable_unlock();
1522 break;
1523 case BROWSER_VIEW:
1524 break;
1525 case FILTERS_VIEW:
1526 filters_toggle_filter();
1527 break;
1528 case HELP_VIEW:
1529 help_toggle();
1530 break;
1534 static struct window *current_win(void)
1536 switch (cur_view) {
1537 case TREE_VIEW:
1538 return lib_cur_win;
1539 case SORTED_VIEW:
1540 return lib_editable.win;
1541 case PLAYLIST_VIEW:
1542 return pl_editable.win;
1543 case QUEUE_VIEW:
1544 return pq_editable.win;
1545 case BROWSER_VIEW:
1546 return browser_win;
1547 case HELP_VIEW:
1548 return help_win;
1549 case FILTERS_VIEW:
1550 default:
1551 return filters_win;
1555 static void cmd_win_bottom(char *arg)
1557 editable_lock();
1558 window_goto_bottom(current_win());
1559 editable_unlock();
1562 static void cmd_win_down(char *arg)
1564 editable_lock();
1565 window_down(current_win(), 1);
1566 editable_unlock();
1569 static void cmd_win_next(char *arg)
1571 if (cur_view == TREE_VIEW) {
1572 editable_lock();
1573 tree_toggle_active_window();
1574 editable_unlock();
1578 static void cmd_win_pg_down(char *arg)
1580 editable_lock();
1581 window_page_down(current_win());
1582 editable_unlock();
1585 static void cmd_win_pg_up(char *arg)
1587 editable_lock();
1588 window_page_up(current_win());
1589 editable_unlock();
1592 static void cmd_win_top(char *arg)
1594 editable_lock();
1595 window_goto_top(current_win());
1596 editable_unlock();
1599 static void cmd_win_up(char *arg)
1601 editable_lock();
1602 window_up(current_win(), 1);
1603 editable_unlock();
1606 static void cmd_win_update(char *arg)
1608 switch (cur_view) {
1609 case TREE_VIEW:
1610 case SORTED_VIEW:
1611 cmus_update_lib();
1612 break;
1613 case BROWSER_VIEW:
1614 browser_reload();
1615 break;
1619 static void cmd_browser_up(char *arg)
1621 browser_up();
1624 static void cmd_refresh(char *arg)
1626 clearok(curscr, TRUE);
1627 refresh();
1630 static int cmp_intp(const void *ap, const void *bp)
1632 int a = *(int *)ap;
1633 int b = *(int *)bp;
1634 return a - b;
1637 static int *rand_array(int size, int nmax)
1639 int *r = xnew(int, size + 1);
1640 int i, offset = 0;
1641 int count = size;
1643 if (count > nmax / 2) {
1645 * Imagine that there are 1000 tracks in library and we want to
1646 * add 998 random tracks to queue. After we have added 997
1647 * random numbers to the array it would be quite hard to find a
1648 * random number that isn't already in the array (3/1000
1649 * probability).
1651 * So we invert the logic:
1653 * Find two (1000 - 998) random numbers in 0..999 range and put
1654 * them at end of the array. Sort the numbers and then fill
1655 * the array starting at index 0 with incrementing values that
1656 * are not in the set of random numbers.
1658 count = nmax - count;
1659 offset = size - count;
1662 for (i = 0; i < count; ) {
1663 int v, j;
1664 found:
1665 v = rand() % nmax;
1666 for (j = 0; j < i; j++) {
1667 if (r[offset + j] == v)
1668 goto found;
1670 r[offset + i++] = v;
1672 qsort(r + offset, count, sizeof(*r), cmp_intp);
1674 if (offset) {
1675 int j, n;
1677 /* simplifies next loop */
1678 r[size] = nmax;
1680 /* convert the indexes we don't want to those we want */
1681 i = 0;
1682 j = offset;
1683 n = 0;
1684 do {
1685 while (n < r[j])
1686 r[i++] = n++;
1687 j++;
1688 n++;
1689 } while (i < size);
1691 return r;
1694 static int count_albums(void)
1696 struct artist *artist;
1697 struct list_head *item;
1698 int count = 0;
1700 list_for_each_entry(artist, &lib_artist_head, node) {
1701 list_for_each(item, &artist->album_head)
1702 count++;
1704 return count;
1707 struct album_list {
1708 struct list_head node;
1709 const struct album *album;
1712 static void cmd_lqueue(char *arg)
1714 LIST_HEAD(head);
1715 const struct list_head *item;
1716 const struct album *album;
1717 int count = 1, nmax, i, pos;
1718 int *r;
1720 if (arg) {
1721 long int val;
1723 if (str_to_int(arg, &val) || val <= 0) {
1724 error_msg("argument must be positive integer");
1725 return;
1727 count = val;
1729 editable_lock();
1730 nmax = count_albums();
1731 if (count > nmax)
1732 count = nmax;
1733 if (!count)
1734 goto unlock;
1736 r = rand_array(count, nmax);
1737 album = to_album(to_artist(lib_artist_head.next)->album_head.next);
1738 pos = 0;
1739 for (i = 0; i < count; i++) {
1740 struct album_list *a;
1742 while (pos < r[i]) {
1743 struct artist *artist = album->artist;
1744 if (album->node.next == &artist->album_head) {
1745 artist = to_artist(artist->node.next);
1746 album = to_album(artist->album_head.next);
1747 } else {
1748 album = to_album(album->node.next);
1750 pos++;
1752 a = xnew(struct album_list, 1);
1753 a->album = album;
1754 list_add_rand(&head, &a->node, i);
1756 free(r);
1758 item = head.next;
1759 do {
1760 struct list_head *next = item->next;
1761 struct album_list *a = container_of(item, struct album_list, node);
1762 struct tree_track *t;
1764 list_for_each_entry(t, &a->album->track_head, node)
1765 editable_add(&pq_editable, simple_track_new(tree_track_info(t)));
1766 free(a);
1767 item = next;
1768 } while (item != &head);
1769 unlock:
1770 editable_unlock();
1773 static void cmd_tqueue(char *arg)
1775 LIST_HEAD(head);
1776 struct list_head *item;
1777 int count = 1, i, pos;
1778 int *r;
1780 if (arg) {
1781 long int val;
1783 if (str_to_int(arg, &val) || val <= 0) {
1784 error_msg("argument must be positive integer");
1785 return;
1787 count = val;
1789 editable_lock();
1790 if (count > lib_editable.nr_tracks)
1791 count = lib_editable.nr_tracks;
1792 if (!count)
1793 goto unlock;
1795 r = rand_array(count, lib_editable.nr_tracks);
1796 item = lib_editable.head.next;
1797 pos = 0;
1798 for (i = 0; i < count; i++) {
1799 struct simple_track *t;
1801 while (pos < r[i]) {
1802 item = item->next;
1803 pos++;
1805 t = simple_track_new(to_simple_track(item)->info);
1806 list_add_rand(&head, &t->node, i);
1808 free(r);
1810 item = head.next;
1811 do {
1812 struct list_head *next = item->next;
1813 struct simple_track *t = to_simple_track(item);
1814 editable_add(&pq_editable, t);
1815 item = next;
1816 } while (item != &head);
1817 unlock:
1818 editable_unlock();
1821 /* tab exp {{{
1823 * these functions fill tabexp struct, which is resetted beforehand
1826 /* buffer used for tab expansion */
1827 static char expbuf[512];
1829 static int filter_directories(const char *name, const struct stat *s)
1831 return S_ISDIR(s->st_mode);
1834 static int filter_any(const char *name, const struct stat *s)
1836 return 1;
1839 static int filter_playable(const char *name, const struct stat *s)
1841 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1844 static int filter_playlist(const char *name, const struct stat *s)
1846 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1849 static int filter_supported(const char *name, const struct stat *s)
1851 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1854 static void expand_files(const char *str)
1856 expand_files_and_dirs(str, filter_any);
1859 static void expand_directories(const char *str)
1861 expand_files_and_dirs(str, filter_directories);
1864 static void expand_playable(const char *str)
1866 expand_files_and_dirs(str, filter_playable);
1869 static void expand_playlist(const char *str)
1871 expand_files_and_dirs(str, filter_playlist);
1874 static void expand_supported(const char *str)
1876 expand_files_and_dirs(str, filter_supported);
1879 static void expand_add(const char *str)
1881 int flag = parse_flags(&str, "lpqQ");
1883 if (flag == -1)
1884 return;
1885 if (str == NULL)
1886 str = "";
1887 expand_supported(str);
1889 if (tabexp.head && flag) {
1890 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1891 free(tabexp.head);
1892 tabexp.head = xstrdup(expbuf);
1896 static void expand_load_save(const char *str)
1898 int flag = parse_flags(&str, "lp");
1900 if (flag == -1)
1901 return;
1902 if (str == NULL)
1903 str = "";
1904 expand_playlist(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_key_context(const char *str, const char *force)
1915 int pos, i, len = strlen(str);
1916 char **tails;
1918 tails = xnew(char *, NR_CTXS + 1);
1919 pos = 0;
1920 for (i = 0; key_context_names[i]; i++) {
1921 int cmp = strncmp(str, key_context_names[i], len);
1922 if (cmp > 0)
1923 continue;
1924 if (cmp < 0)
1925 break;
1926 tails[pos++] = xstrdup(key_context_names[i] + len);
1929 if (pos == 0) {
1930 free(tails);
1931 return;
1933 if (pos == 1) {
1934 char *tmp = xstrjoin(tails[0], " ");
1935 free(tails[0]);
1936 tails[0] = tmp;
1938 tails[pos] = NULL;
1939 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1940 tabexp.head = xstrdup(expbuf);
1941 tabexp.tails = tails;
1944 static int get_context(const char *str, int len)
1946 int i, c = -1, count = 0;
1948 for (i = 0; key_context_names[i]; i++) {
1949 if (strncmp(str, key_context_names[i], len) == 0) {
1950 if (key_context_names[i][len] == 0) {
1951 /* exact */
1952 return i;
1954 c = i;
1955 count++;
1958 if (count == 1)
1959 return c;
1960 return -1;
1963 static void expand_command_line(const char *str);
1965 static void expand_bind_args(const char *str)
1967 /* :bind context key function
1969 * possible values for str:
1971 * context k
1972 * context key f
1974 * you need to know context before you can expand function
1976 /* start and end pointers for context, key and function */
1977 const char *cs, *ce, *ks, *ke, *fs;
1978 int i, c, k, count;
1979 int flag = parse_flags((const char **)&str, "f");
1980 const char *force = "";
1982 if (flag == -1)
1983 return;
1984 if (str == NULL)
1985 str = "";
1987 if (flag == 'f')
1988 force = "-f ";
1990 cs = str;
1991 ce = strchr(cs, ' ');
1992 if (ce == NULL) {
1993 expand_key_context(cs, force);
1994 return;
1997 /* context must be expandable */
1998 c = get_context(cs, ce - cs);
1999 if (c == -1) {
2000 /* context is ambiguous or invalid */
2001 return;
2004 ks = ce;
2005 while (*ks == ' ')
2006 ks++;
2007 ke = strchr(ks, ' ');
2008 if (ke == NULL) {
2009 /* expand key */
2010 int len = strlen(ks);
2011 PTR_ARRAY(array);
2013 for (i = 0; key_table[i].name; i++) {
2014 int cmp = strncmp(ks, key_table[i].name, len);
2015 if (cmp > 0)
2016 continue;
2017 if (cmp < 0)
2018 break;
2019 ptr_array_add(&array, xstrdup(key_table[i].name + len));
2022 if (!array.count)
2023 return;
2025 if (array.count == 1) {
2026 char **ptrs = array.ptrs;
2027 char *tmp = xstrjoin(ptrs[0], " ");
2028 free(ptrs[0]);
2029 ptrs[0] = tmp;
2032 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
2034 ptr_array_plug(&array);
2035 tabexp.head = xstrdup(expbuf);
2036 tabexp.tails = array.ptrs;
2037 return;
2040 /* key must be expandable */
2041 k = -1;
2042 count = 0;
2043 for (i = 0; key_table[i].name; i++) {
2044 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
2045 if (key_table[i].name[ke - ks] == 0) {
2046 /* exact */
2047 k = i;
2048 count = 1;
2049 break;
2051 k = i;
2052 count++;
2055 if (count != 1) {
2056 /* key is ambiguous or invalid */
2057 return;
2060 fs = ke;
2061 while (*fs == ' ')
2062 fs++;
2064 if (*fs == ':')
2065 fs++;
2067 /* expand com [arg...] */
2068 expand_command_line(fs);
2069 if (tabexp.head == NULL) {
2070 /* command expand failed */
2071 return;
2075 * tabexp.head is now "com"
2076 * tabexp.tails is [ mand1 mand2 ... ]
2078 * need to change tabexp.head to "context key com"
2081 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
2082 key_table[k].name, tabexp.head);
2083 free(tabexp.head);
2084 tabexp.head = xstrdup(expbuf);
2087 static void expand_unbind_args(const char *str)
2089 /* :unbind context key */
2090 /* start and end pointers for context and key */
2091 const char *cs, *ce, *ks;
2092 const struct binding *b;
2093 PTR_ARRAY(array);
2094 int c, len;
2096 cs = str;
2097 ce = strchr(cs, ' ');
2098 if (ce == NULL) {
2099 expand_key_context(cs, "");
2100 return;
2103 /* context must be expandable */
2104 c = get_context(cs, ce - cs);
2105 if (c == -1) {
2106 /* context is ambiguous or invalid */
2107 return;
2110 ks = ce;
2111 while (*ks == ' ')
2112 ks++;
2114 /* expand key */
2115 len = strlen(ks);
2116 b = key_bindings[c];
2117 while (b) {
2118 if (!strncmp(ks, b->key->name, len))
2119 ptr_array_add(&array, xstrdup(b->key->name + len));
2120 b = b->next;
2122 if (!array.count)
2123 return;
2125 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
2127 ptr_array_plug(&array);
2128 tabexp.head = xstrdup(expbuf);
2129 tabexp.tails = array.ptrs;
2132 static void expand_factivate(const char *str)
2134 /* "name1 name2 name3", expand only name3 */
2135 struct filter_entry *e;
2136 const char *name;
2137 PTR_ARRAY(array);
2138 int str_len, len, i;
2140 str_len = strlen(str);
2141 i = str_len;
2142 while (i > 0) {
2143 if (str[i - 1] == ' ')
2144 break;
2145 i--;
2147 len = str_len - i;
2148 name = str + i;
2150 list_for_each_entry(e, &filters_head, node) {
2151 if (!strncmp(name, e->name, len))
2152 ptr_array_add(&array, xstrdup(e->name + len));
2154 if (!array.count)
2155 return;
2157 ptr_array_plug(&array);
2158 tabexp.head = xstrdup(str);
2159 tabexp.tails = array.ptrs;
2162 static void expand_options(const char *str)
2164 struct cmus_opt *opt;
2165 int len;
2166 char **tails;
2168 /* tabexp is resetted */
2169 len = strlen(str);
2170 if (len > 1 && str[len - 1] == '=') {
2171 /* expand value */
2172 char *var = xstrndup(str, len - 1);
2174 list_for_each_entry(opt, &option_head, node) {
2175 if (strcmp(var, opt->name) == 0) {
2176 char buf[OPTION_MAX_SIZE];
2178 tails = xnew(char *, 2);
2180 buf[0] = 0;
2181 opt->get(opt->id, buf);
2182 tails[0] = xstrdup(buf);
2183 tails[1] = NULL;
2185 tabexp.head = xstrdup(str);
2186 tabexp.tails = tails;
2187 free(var);
2188 return;
2191 free(var);
2192 } else {
2193 /* expand variable */
2194 int pos;
2196 tails = xnew(char *, nr_options + 1);
2197 pos = 0;
2198 list_for_each_entry(opt, &option_head, node) {
2199 if (strncmp(str, opt->name, len) == 0)
2200 tails[pos++] = xstrdup(opt->name + len);
2202 if (pos > 0) {
2203 if (pos == 1) {
2204 /* only one variable matches, add '=' */
2205 char *tmp = xstrjoin(tails[0], "=");
2207 free(tails[0]);
2208 tails[0] = tmp;
2211 tails[pos] = NULL;
2212 tabexp.head = xstrdup(str);
2213 tabexp.tails = tails;
2214 } else {
2215 free(tails);
2220 static void expand_toptions(const char *str)
2222 struct cmus_opt *opt;
2223 int len, pos;
2224 char **tails;
2226 tails = xnew(char *, nr_options + 1);
2227 len = strlen(str);
2228 pos = 0;
2229 list_for_each_entry(opt, &option_head, node) {
2230 if (opt->toggle == NULL)
2231 continue;
2232 if (strncmp(str, opt->name, len) == 0)
2233 tails[pos++] = xstrdup(opt->name + len);
2235 if (pos > 0) {
2236 tails[pos] = NULL;
2237 tabexp.head = xstrdup(str);
2238 tabexp.tails = tails;
2239 } else {
2240 free(tails);
2244 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2246 struct directory dir;
2247 const char *name, *dot;
2248 int len = strlen(str);
2250 if (dir_open(&dir, dirname))
2251 return;
2253 while ((name = dir_read(&dir))) {
2254 if (!S_ISREG(dir.st.st_mode))
2255 continue;
2256 if (strncmp(name, str, len))
2257 continue;
2258 dot = strrchr(name, '.');
2259 if (dot == NULL || strcmp(dot, ".theme"))
2260 continue;
2261 if (dot - name < len)
2262 /* str is "foo.th"
2263 * matches "foo.theme"
2264 * which also ends with ".theme"
2266 continue;
2267 ptr_array_add(array, xstrndup(name + len, dot - name - len));
2269 dir_close(&dir);
2272 static void expand_colorscheme(const char *str)
2274 PTR_ARRAY(array);
2276 load_themes(cmus_config_dir, str, &array);
2277 load_themes(DATADIR "/cmus", str, &array);
2279 if (array.count) {
2280 ptr_array_sort(&array, strptrcmp);
2282 ptr_array_plug(&array);
2283 tabexp.head = xstrdup(str);
2284 tabexp.tails = array.ptrs;
2288 /* tab exp }}} */
2290 /* sort by name */
2291 struct command commands[] = {
2292 { "add", cmd_add, 1, 1, expand_add, 0, 0 },
2293 { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
2294 { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
2295 { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
2296 { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
2297 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
2298 { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
2299 { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
2300 { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
2301 { "fset", cmd_fset, 1, 1, NULL, 0, 0 },
2302 { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
2303 { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
2304 { "lqueue", cmd_lqueue, 0, 1, NULL, 0, 0 },
2305 { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
2306 { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
2307 { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
2308 { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
2309 { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
2310 { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
2311 { "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
2312 { "quit", cmd_quit, 0, 0, NULL, 0, 0 },
2313 { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
2314 { "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
2315 { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
2316 { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
2317 { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
2318 { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
2319 { "set", cmd_set, 1, 1, expand_options, 0, 0 },
2320 { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
2321 { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
2322 { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
2323 { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
2324 { "tqueue", cmd_tqueue, 0, 1, NULL, 0, 0 },
2325 { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
2326 { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
2327 { "view", cmd_view, 1, 1, NULL, 0, 0 },
2328 { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
2329 { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
2330 { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
2331 { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
2332 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
2333 { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
2334 { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
2335 { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
2336 { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
2337 { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
2338 { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
2339 { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
2340 { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
2341 { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
2342 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
2343 { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
2344 { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
2345 { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
2346 { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
2347 { NULL, NULL, 0, 0, 0, 0, 0 }
2350 /* fills tabexp struct */
2351 static void expand_commands(const char *str)
2353 int i, len, pos;
2354 char **tails;
2356 /* tabexp is resetted */
2357 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2358 len = strlen(str);
2359 pos = 0;
2360 for (i = 0; commands[i].name; i++) {
2361 if (strncmp(str, commands[i].name, len) == 0)
2362 tails[pos++] = xstrdup(commands[i].name + len);
2364 if (pos > 0) {
2365 if (pos == 1) {
2366 /* only one command matches, add ' ' */
2367 char *tmp = xstrjoin(tails[0], " ");
2369 free(tails[0]);
2370 tails[0] = tmp;
2372 tails[pos] = NULL;
2373 tabexp.head = xstrdup(str);
2374 tabexp.tails = tails;
2375 } else {
2376 free(tails);
2380 struct command *get_command(const char *str)
2382 int i, len;
2384 while (*str == ' ')
2385 str++;
2386 for (len = 0; str[len] && str[len] != ' '; len++)
2389 for (i = 0; commands[i].name; i++) {
2390 if (strncmp(str, commands[i].name, len))
2391 continue;
2393 if (commands[i].name[len] == 0) {
2394 /* exact */
2395 return &commands[i];
2398 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2399 /* ambiguous */
2400 return NULL;
2402 return &commands[i];
2404 return NULL;
2407 /* fills tabexp struct */
2408 static void expand_command_line(const char *str)
2410 /* :command [arg]...
2412 * examples:
2414 * str expanded value (tabexp.head)
2415 * -------------------------------------
2416 * fs fset
2417 * b c bind common
2418 * se se (tabexp.tails = [ ek t ])
2420 /* command start/end, argument start */
2421 const char *cs, *ce, *as;
2422 const struct command *cmd;
2424 cs = str;
2425 ce = strchr(cs, ' ');
2426 if (ce == NULL) {
2427 /* expand command */
2428 expand_commands(cs);
2429 return;
2432 /* command must be expandable */
2433 cmd = get_command(cs);
2434 if (cmd == NULL) {
2435 /* command ambiguous or invalid */
2436 return;
2439 if (cmd->expand == NULL) {
2440 /* can't expand argument */
2441 return;
2444 as = ce;
2445 while (*as == ' ')
2446 as++;
2448 /* expand argument */
2449 cmd->expand(as);
2450 if (tabexp.head == NULL) {
2451 /* argument expansion failed */
2452 return;
2455 /* tabexp.head is now start of the argument string */
2456 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2457 free(tabexp.head);
2458 tabexp.head = xstrdup(expbuf);
2461 static void tab_expand(void)
2463 char *s1, *s2, *tmp;
2464 int pos;
2466 /* strip white space */
2467 pos = 0;
2468 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2469 pos++;
2471 /* string to expand */
2472 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2474 /* tail */
2475 s2 = xstrdup(cmdline.line + cmdline.bpos);
2477 tmp = tabexp_expand(s1, expand_command_line);
2478 if (tmp) {
2479 /* tmp.s2 */
2480 int l1, l2;
2482 l1 = strlen(tmp);
2483 l2 = strlen(s2);
2484 cmdline.blen = l1 + l2;
2485 if (cmdline.blen >= cmdline.size) {
2486 while (cmdline.blen >= cmdline.size)
2487 cmdline.size *= 2;
2488 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2490 sprintf(cmdline.line, "%s%s", tmp, s2);
2491 cmdline.bpos = l1;
2492 cmdline.cpos = u_strlen(tmp);
2493 cmdline.clen = u_strlen(cmdline.line);
2494 free(tmp);
2496 free(s1);
2497 free(s2);
2500 static void reset_tab_expansion(void)
2502 tabexp_reset();
2503 arg_expand_cmd = -1;
2506 int run_only_safe_commands;
2508 /* FIXME: parse all arguments */
2509 void run_command(const char *buf)
2511 char *cmd, *arg;
2512 int cmd_start, cmd_end, cmd_len;
2513 int arg_start, arg_end;
2514 int i;
2516 i = 0;
2517 while (buf[i] && buf[i] == ' ')
2518 i++;
2520 if (buf[i] == '#')
2521 return;
2523 cmd_start = i;
2524 while (buf[i] && buf[i] != ' ')
2525 i++;
2526 cmd_end = i;
2527 while (buf[i] && buf[i] == ' ')
2528 i++;
2529 arg_start = i;
2530 while (buf[i])
2531 i++;
2532 arg_end = i;
2534 cmd_len = cmd_end - cmd_start;
2535 if (cmd_len == 0)
2536 return;
2538 cmd = xstrndup(buf + cmd_start, cmd_len);
2539 if (arg_start == arg_end) {
2540 arg = NULL;
2541 } else {
2542 arg = xstrndup(buf + arg_start, arg_end - arg_start);
2544 i = 0;
2545 while (1) {
2546 const struct command *c = &commands[i];
2548 if (c->name == NULL) {
2549 error_msg("unknown command\n");
2550 break;
2552 if (strncmp(cmd, c->name, cmd_len) == 0) {
2553 const char *next = commands[i + 1].name;
2554 int exact = c->name[cmd_len] == 0;
2556 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2557 error_msg("ambiguous command\n");
2558 break;
2560 if (c->min_args > 0 && arg == NULL) {
2561 error_msg("not enough arguments\n");
2562 break;
2564 if (c->max_args == 0 && arg) {
2565 error_msg("too many arguments\n");
2566 break;
2568 if (run_only_safe_commands && c->flags & CMD_UNSAFE) {
2569 d_print("trying to execute unsafe command over net\n");
2570 break;
2572 c->func(arg);
2573 break;
2575 i++;
2577 free(arg);
2578 free(cmd);
2581 static void reset_history_search(void)
2583 history_reset_search(&cmd_history);
2584 free(history_search_text);
2585 history_search_text = NULL;
2588 static void backspace(void)
2590 if (cmdline.clen > 0) {
2591 cmdline_backspace();
2592 } else {
2593 input_mode = NORMAL_MODE;
2597 void command_mode_ch(uchar ch)
2599 switch (ch) {
2600 case 0x01: // ^A
2601 cmdline_move_home();
2602 break;
2603 case 0x02: // ^B
2604 cmdline_move_left();
2605 break;
2606 case 0x04: // ^D
2607 cmdline_delete_ch();
2608 break;
2609 case 0x05: // ^E
2610 cmdline_move_end();
2611 break;
2612 case 0x06: // ^F
2613 cmdline_move_right();
2614 break;
2615 case 0x03: // ^C
2616 case 0x07: // ^G
2617 case 0x1B: // ESC
2618 if (cmdline.blen) {
2619 history_add_line(&cmd_history, cmdline.line);
2620 cmdline_clear();
2622 input_mode = NORMAL_MODE;
2623 break;
2624 case 0x0A:
2625 if (cmdline.blen) {
2626 run_command(cmdline.line);
2627 history_add_line(&cmd_history, cmdline.line);
2628 cmdline_clear();
2630 input_mode = NORMAL_MODE;
2631 break;
2632 case 0x0B:
2633 cmdline_clear_end();
2634 break;
2635 case 0x09:
2636 tab_expand();
2637 break;
2638 case 0x15:
2639 cmdline_backspace_to_bol();
2640 break;
2641 case 127:
2642 backspace();
2643 break;
2644 default:
2645 cmdline_insert_ch(ch);
2647 reset_history_search();
2648 if (ch != 0x09)
2649 reset_tab_expansion();
2652 void command_mode_key(int key)
2654 reset_tab_expansion();
2655 switch (key) {
2656 case KEY_DC:
2657 cmdline_delete_ch();
2658 break;
2659 case KEY_BACKSPACE:
2660 backspace();
2661 break;
2662 case KEY_LEFT:
2663 cmdline_move_left();
2664 return;
2665 case KEY_RIGHT:
2666 cmdline_move_right();
2667 return;
2668 case KEY_HOME:
2669 cmdline_move_home();
2670 return;
2671 case KEY_END:
2672 cmdline_move_end();
2673 return;
2674 case KEY_UP:
2676 const char *s;
2678 if (history_search_text == NULL)
2679 history_search_text = xstrdup(cmdline.line);
2680 s = history_search_forward(&cmd_history, history_search_text);
2681 if (s)
2682 cmdline_set_text(s);
2684 return;
2685 case KEY_DOWN:
2686 if (history_search_text) {
2687 const char *s;
2689 s = history_search_backward(&cmd_history, history_search_text);
2690 if (s) {
2691 cmdline_set_text(s);
2692 } else {
2693 cmdline_set_text(history_search_text);
2696 return;
2697 default:
2698 d_print("key = %c (%d)\n", key, key);
2700 reset_history_search();
2703 void commands_init(void)
2705 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2706 history_load(&cmd_history, cmd_history_filename, 2000);
2709 void commands_exit(void)
2711 history_save(&cmd_history);
2712 free(cmd_history_filename);
2713 tabexp_reset();