Use ~/.cmus/socket instead of /tmp/cmus-$USER
[cmus.git] / command_mode.c
blobc589aca9d368a942b453768b243e7aae2f13c715
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 "config/datadir.h"
49 #include <stdlib.h>
50 #include <curses.h>
51 #include <ctype.h>
52 #include <sys/types.h>
53 #include <sys/wait.h>
54 #include <dirent.h>
55 #include <pwd.h>
57 static struct history cmd_history;
58 static char *cmd_history_filename;
59 static char *history_search_text = NULL;
60 static int arg_expand_cmd = -1;
62 static char *get_home_dir(const char *username)
64 struct passwd *passwd;
66 if (username == NULL)
67 return xstrdup(home_dir);
68 passwd = getpwnam(username);
69 if (passwd == NULL)
70 return NULL;
71 /* don't free passwd */
72 return xstrdup(passwd->pw_dir);
75 static char *expand_filename(const char *name)
77 if (name[0] == '~') {
78 char *slash;
80 slash = strchr(name, '/');
81 if (slash) {
82 char *username, *home;
84 if (slash - name - 1 > 0) {
85 /* ~user/... */
86 username = xstrndup(name + 1, slash - name - 1);
87 } else {
88 /* ~/... */
89 username = NULL;
91 home = get_home_dir(username);
92 free(username);
93 if (home) {
94 char *expanded;
96 expanded = xstrjoin(home, slash);
97 free(home);
98 return expanded;
99 } else {
100 return xstrdup(name);
102 } else {
103 if (name[1] == 0) {
104 return xstrdup(home_dir);
105 } else {
106 char *home;
108 home = get_home_dir(name + 1);
109 if (home)
110 return home;
111 return xstrdup(name);
114 } else {
115 return xstrdup(name);
119 /* view {{{ */
121 void view_clear(int view)
123 switch (view) {
124 case TREE_VIEW:
125 case SORTED_VIEW:
126 worker_remove_jobs(JOB_TYPE_LIB);
127 editable_lock();
128 editable_clear(&lib_editable);
130 /* FIXME: make this optional? */
131 lib_clear_store();
133 editable_unlock();
134 break;
135 case PLAYLIST_VIEW:
136 worker_remove_jobs(JOB_TYPE_PL);
137 editable_lock();
138 editable_clear(&pl_editable);
139 editable_unlock();
140 break;
141 case QUEUE_VIEW:
142 worker_remove_jobs(JOB_TYPE_QUEUE);
143 editable_lock();
144 editable_clear(&pq_editable);
145 editable_unlock();
146 break;
147 default:
148 info_msg(":clear only works in views 1-4");
152 void view_add(int view, char *arg, int prepend)
154 char *tmp, *name;
155 enum file_type ft;
157 tmp = expand_filename(arg);
158 ft = cmus_detect_ft(tmp, &name);
159 if (ft == FILE_TYPE_INVALID) {
160 error_msg("adding '%s': %s", tmp, strerror(errno));
161 free(tmp);
162 return;
164 free(tmp);
166 switch (view) {
167 case TREE_VIEW:
168 case SORTED_VIEW:
169 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
170 break;
171 case PLAYLIST_VIEW:
172 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
173 break;
174 case QUEUE_VIEW:
175 if (prepend) {
176 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
177 } else {
178 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
180 break;
181 default:
182 info_msg(":add only works in views 1-4");
184 free(name);
187 void view_load(int view, char *arg)
189 char *tmp, *name;
190 enum file_type ft;
192 tmp = expand_filename(arg);
193 ft = cmus_detect_ft(tmp, &name);
194 if (ft == FILE_TYPE_INVALID) {
195 error_msg("loading '%s': %s", tmp, strerror(errno));
196 free(tmp);
197 return;
199 free(tmp);
201 if (ft == FILE_TYPE_FILE)
202 ft = FILE_TYPE_PL;
203 if (ft != FILE_TYPE_PL) {
204 error_msg("loading '%s': not a playlist file", name);
205 free(name);
206 return;
209 switch (view) {
210 case TREE_VIEW:
211 case SORTED_VIEW:
212 worker_remove_jobs(JOB_TYPE_LIB);
213 editable_lock();
214 editable_clear(&lib_editable);
215 editable_unlock();
216 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
217 free(lib_filename);
218 lib_filename = name;
219 break;
220 case PLAYLIST_VIEW:
221 worker_remove_jobs(JOB_TYPE_PL);
222 editable_lock();
223 editable_clear(&pl_editable);
224 editable_unlock();
225 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
226 free(pl_filename);
227 pl_filename = name;
228 break;
229 default:
230 info_msg(":load only works in views 1-3");
231 free(name);
235 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
237 char *filename = *filenamep;
239 if (arg) {
240 free(filename);
241 filename = xstrdup(arg);
242 *filenamep = filename;
245 editable_lock();
246 if (cmus_save(for_each_ti, filename) == -1)
247 error_msg("saving '%s': %s", filename, strerror(errno));
248 editable_unlock();
251 void view_save(int view, char *arg)
253 if (arg) {
254 char *tmp;
256 tmp = expand_filename(arg);
257 arg = path_absolute(tmp);
258 free(tmp);
261 switch (view) {
262 case TREE_VIEW:
263 case SORTED_VIEW:
264 do_save(lib_for_each, arg, &lib_filename);
265 break;
266 case PLAYLIST_VIEW:
267 do_save(pl_for_each, arg, &pl_filename);
268 break;
269 default:
270 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
272 free(arg);
275 /* }}} */
277 /* only returns the last flag which is enough for it's callers */
278 static int parse_flags(const char **strp, const char *flags)
280 const char *str = *strp;
281 int flag = 0;
283 if (str == NULL)
284 return flag;
286 while (*str) {
287 if (*str != '-')
288 break;
290 // "-"
291 if (str[1] == 0)
292 break;
294 // "--" or "-- "
295 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
296 str += 2;
297 break;
300 // not "-?" or "-? "
301 if (str[2] && str[2] != ' ')
302 break;
304 flag = str[1];
305 if (!strchr(flags, flag)) {
306 error_msg("invalid option -%c", flag);
307 return -1;
310 str += 2;
312 while (*str == ' ')
313 str++;
315 while (*str == ' ')
316 str++;
317 if (*str == 0)
318 str = NULL;
319 *strp = str;
320 return flag;
323 static int flag_to_view(int flag)
325 switch (flag) {
326 case 'l':
327 return TREE_VIEW;
328 case 'p':
329 return PLAYLIST_VIEW;
330 case 'q':
331 case 'Q':
332 return QUEUE_VIEW;
333 default:
334 return cur_view;
338 static void cmd_add(char *arg)
340 int flag = parse_flags((const char **)&arg, "lpqQ");
342 if (flag == -1)
343 return;
344 if (arg == NULL) {
345 error_msg("not enough arguments\n");
346 return;
348 view_add(flag_to_view(flag), arg, flag == 'Q');
351 static void cmd_clear(char *arg)
353 int flag = parse_flags((const char **)&arg, "lpq");
355 if (flag == -1)
356 return;
357 if (arg) {
358 error_msg("too many arguments\n");
359 return;
361 view_clear(flag_to_view(flag));
364 static void cmd_load(char *arg)
366 int flag = parse_flags((const char **)&arg, "lp");
368 if (flag == -1)
369 return;
370 if (arg == NULL) {
371 error_msg("not enough arguments\n");
372 return;
374 view_load(flag_to_view(flag), arg);
377 static void cmd_save(char *arg)
379 int flag = parse_flags((const char **)&arg, "lp");
381 if (flag == -1)
382 return;
383 view_save(flag_to_view(flag), arg);
386 static void cmd_set(char *arg)
388 char *value = NULL;
389 int i;
391 for (i = 0; arg[i]; i++) {
392 if (arg[i] == '=') {
393 arg[i] = 0;
394 value = &arg[i + 1];
395 break;
398 if (value) {
399 option_set(arg, value);
400 } else {
401 struct cmus_opt *opt;
402 char buf[OPTION_MAX_SIZE];
404 /* support "set <option>?" */
405 i--;
406 if (arg[i] == '?')
407 arg[i] = 0;
409 opt = option_find(arg);
410 if (opt) {
411 opt->get(opt->id, buf);
412 info_msg("setting: '%s=%s'", arg, buf);
417 static void cmd_toggle(char *arg)
419 struct cmus_opt *opt = option_find(arg);
421 if (opt == NULL)
422 return;
424 if (opt->toggle == NULL) {
425 error_msg("%s is not toggle option", opt->name);
426 return;
428 opt->toggle(opt->id);
431 static void cmd_seek(char *arg)
433 int seek, seek_mode;
434 char *endptr;
436 /* Absolute or relative search */
437 seek_mode = SEEK_SET;
438 if (arg[0] == '+' || arg[0] == '-') {
439 seek_mode = SEEK_CUR;
442 seek = (int) strtol(arg, &endptr, 10);
443 if (!seek && arg == endptr) {
444 error_msg("invalid seek value");
445 return;
448 /* Expand M, H to seconds */
449 if (endptr[0] && toupper(endptr[0]) != 'S') {
450 endptr[0] = toupper (endptr[0]);
451 if (endptr[0] == 'M') {
452 seek *= 60;
453 } else if (endptr[0] == 'H') {
454 seek *= 3600;
455 } else {
456 error_msg("invalid seek modifier");
457 return;
461 player_seek(seek, seek_mode);
464 static void cmd_factivate(char *arg)
466 editable_lock();
467 filters_activate_names(arg);
468 editable_unlock();
471 static void cmd_filter(char *arg)
473 editable_lock();
474 filters_set_anonymous(arg);
475 editable_unlock();
478 static void cmd_fset(char *arg)
480 filters_set_filter(arg);
483 static void cmd_invert(char *arg)
485 editable_lock();
486 switch (cur_view) {
487 case SORTED_VIEW:
488 editable_invert_marks(&lib_editable);
489 break;
490 case PLAYLIST_VIEW:
491 editable_invert_marks(&pl_editable);
492 break;
493 case QUEUE_VIEW:
494 editable_invert_marks(&pq_editable);
495 break;
496 default:
497 info_msg(":invert only works in views 2-4");
499 editable_unlock();
502 static void cmd_mark(char *arg)
504 editable_lock();
505 switch (cur_view) {
506 case SORTED_VIEW:
507 editable_mark(&lib_editable, arg);
508 break;
509 case PLAYLIST_VIEW:
510 editable_mark(&pl_editable, arg);
511 break;
512 case QUEUE_VIEW:
513 editable_mark(&pq_editable, arg);
514 break;
515 default:
516 info_msg(":mark only works in views 2-4");
518 editable_unlock();
521 static void cmd_unmark(char *arg)
523 editable_lock();
524 switch (cur_view) {
525 case SORTED_VIEW:
526 editable_unmark(&lib_editable);
527 break;
528 case PLAYLIST_VIEW:
529 editable_unmark(&pl_editable);
530 break;
531 case QUEUE_VIEW:
532 editable_unmark(&pq_editable);
533 break;
534 default:
535 info_msg(":unmark only works in views 2-4");
537 editable_unlock();
540 static void cmd_cd(char *arg)
542 if (arg) {
543 char *dir, *absolute;
545 dir = expand_filename(arg);
546 absolute = path_absolute(dir);
547 if (chdir(dir) == -1) {
548 error_msg("could not cd to '%s': %s", dir, strerror(errno));
549 } else {
550 browser_chdir(absolute);
552 free(absolute);
553 free(dir);
554 } else {
555 if (chdir(home_dir) == -1) {
556 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
557 } else {
558 browser_chdir(home_dir);
563 static void cmd_bind(char *arg)
565 int flag = parse_flags((const char **)&arg, "f");
566 char *key, *func;
568 if (flag == -1)
569 return;
571 if (arg == NULL)
572 goto err;
574 key = strchr(arg, ' ');
575 if (key == NULL)
576 goto err;
577 *key++ = 0;
578 while (*key == ' ')
579 key++;
581 func = strchr(key, ' ');
582 if (func == NULL)
583 goto err;
584 *func++ = 0;
585 while (*func == ' ')
586 func++;
587 if (*func == 0)
588 goto err;
590 key_bind(arg, key, func, flag == 'f');
591 return;
592 err:
593 error_msg("expecting 3 arguments (context, key and function)\n");
596 static void cmd_unbind(char *arg)
598 int flag = parse_flags((const char **)&arg, "f");
599 char *key;
601 if (flag == -1)
602 return;
604 if (arg == NULL)
605 goto err;
607 key = strchr(arg, ' ');
608 if (key == NULL)
609 goto err;
610 *key++ = 0;
611 while (*key == ' ')
612 key++;
613 if (*key == 0)
614 goto err;
616 /* FIXME: remove spaces at end */
618 key_unbind(arg, key, flag == 'f');
619 return;
620 err:
621 error_msg("expecting 2 arguments (context and key)\n");
624 static void cmd_showbind(char *arg)
626 char *key;
628 key = strchr(arg, ' ');
629 if (key == NULL)
630 goto err;
631 *key++ = 0;
632 while (*key == ' ')
633 key++;
634 if (*key == 0)
635 goto err;
637 /* FIXME: remove spaces at end */
639 show_binding(arg, key);
640 return;
641 err:
642 error_msg("expecting 2 arguments (context and key)\n");
645 static void cmd_quit(char *arg)
647 quit();
650 static void cmd_reshuffle(char *arg)
652 editable_lock();
653 lib_reshuffle();
654 pl_reshuffle();
655 editable_unlock();
658 static void cmd_source(char *arg)
660 char *filename = expand_filename(arg);
662 if (source_file(filename) == -1)
663 error_msg("sourcing %s: %s", filename, strerror(errno));
664 free(filename);
667 static void cmd_colorscheme(char *arg)
669 char filename[512];
671 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
672 if (source_file(filename) == -1) {
673 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
674 if (source_file(filename) == -1)
675 error_msg("sourcing %s: %s", filename, strerror(errno));
680 * \" inside double-quotes becomes "
681 * \\ inside double-quotes becomes \
683 static char *parse_quoted(const char **strp)
685 const char *str = *strp;
686 const char *start;
687 char *ret, *dst;
689 str++;
690 start = str;
691 while (1) {
692 int c = *str++;
694 if (c == 0)
695 goto error;
696 if (c == '"')
697 break;
698 if (c == '\\') {
699 if (*str++ == 0)
700 goto error;
703 *strp = str;
704 ret = xnew(char, str - start);
705 str = start;
706 dst = ret;
707 while (1) {
708 int c = *str++;
710 if (c == '"')
711 break;
712 if (c == '\\') {
713 c = *str++;
714 if (c != '"' && c != '\\')
715 *dst++ = '\\';
717 *dst++ = c;
719 *dst = 0;
720 return ret;
721 error:
722 error_msg("`\"' expected");
723 return NULL;
726 static char *parse_escaped(const char **strp)
728 const char *str = *strp;
729 const char *start;
730 char *ret, *dst;
732 start = str;
733 while (1) {
734 int c = *str;
736 if (c == 0 || c == ' ' || c == '\'' || c == '"')
737 break;
739 str++;
740 if (c == '\\') {
741 c = *str;
742 if (c == 0)
743 break;
744 str++;
747 *strp = str;
748 ret = xnew(char, str - start + 1);
749 str = start;
750 dst = ret;
751 while (1) {
752 int c = *str;
754 if (c == 0 || c == ' ' || c == '\'' || c == '"')
755 break;
757 str++;
758 if (c == '\\') {
759 c = *str;
760 if (c == 0) {
761 *dst++ = '\\';
762 break;
764 str++;
766 *dst++ = c;
768 *dst = 0;
769 return ret;
772 static char *parse_one(const char **strp)
774 const char *str = *strp;
775 char *ret = NULL;
777 while (1) {
778 char *part;
779 int c = *str;
781 if (!c || c == ' ')
782 break;
783 if (c == '"') {
784 part = parse_quoted(&str);
785 if (part == NULL)
786 goto error;
787 } else if (c == '\'') {
788 /* backslashes are normal chars inside single-quotes */
789 const char *end;
791 str++;
792 end = strchr(str, '\'');
793 if (end == NULL)
794 goto sq_missing;
795 part = xstrndup(str, end - str);
796 str = end + 1;
797 } else {
798 part = parse_escaped(&str);
801 if (ret == NULL) {
802 ret = part;
803 } else {
804 char *tmp = xstrjoin(ret, part);
805 free(ret);
806 ret = tmp;
809 *strp = str;
810 return ret;
811 sq_missing:
812 error_msg("`'' expected");
813 error:
814 free(ret);
815 return NULL;
818 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
820 char **av = NULL;
821 int nr = 0;
822 int alloc = 0;
824 while (*cmd) {
825 char *arg;
827 /* there can't be spaces at start of command
828 * and there is at least one argument */
829 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
830 /* {} is replaced with file arguments */
831 if (*args_idx != -1)
832 goto only_once_please;
833 *args_idx = nr;
834 cmd += 2;
835 goto skip_spaces;
836 } else {
837 arg = parse_one(&cmd);
838 if (arg == NULL)
839 goto error;
842 if (nr == alloc) {
843 alloc = alloc ? alloc * 2 : 4;
844 av = xrenew(char *, av, alloc + 1);
846 av[nr++] = arg;
847 skip_spaces:
848 while (*cmd == ' ')
849 cmd++;
851 av[nr] = NULL;
852 *ac = nr;
853 return av;
854 only_once_please:
855 error_msg("{} can be used only once");
856 error:
857 while (nr > 0)
858 free(av[--nr]);
859 free(av);
860 return NULL;
863 static struct track_info **sel_tis;
864 static int sel_tis_alloc;
865 static int sel_tis_nr;
867 static int add_ti(void *data, struct track_info *ti)
869 if (sel_tis_nr == sel_tis_alloc) {
870 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
871 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
873 track_info_ref(ti);
874 sel_tis[sel_tis_nr++] = ti;
875 return 0;
878 static void cmd_run(char *arg)
880 char **av, **argv;
881 int ac, argc, i, run, files_idx = -1;
883 if (cur_view > QUEUE_VIEW) {
884 info_msg("Command execution is supported only in views 1-4");
885 return;
888 av = parse_cmd(arg, &files_idx, &ac);
889 if (av == NULL) {
890 return;
893 /* collect selected files (struct track_info) */
894 sel_tis = NULL;
895 sel_tis_alloc = 0;
896 sel_tis_nr = 0;
898 editable_lock();
899 switch (cur_view) {
900 case TREE_VIEW:
901 __tree_for_each_sel(add_ti, NULL, 0);
902 break;
903 case SORTED_VIEW:
904 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
905 break;
906 case PLAYLIST_VIEW:
907 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
908 break;
909 case QUEUE_VIEW:
910 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
911 break;
913 editable_unlock();
915 if (sel_tis_nr == 0) {
916 /* no files selected, do nothing */
917 free_str_array(av);
918 return;
920 sel_tis[sel_tis_nr] = NULL;
922 /* build argv */
923 argv = xnew(char *, ac + sel_tis_nr + 1);
924 argc = 0;
925 if (files_idx == -1) {
926 /* add selected files after rest of the args */
927 for (i = 0; i < ac; i++)
928 argv[argc++] = av[i];
929 for (i = 0; i < sel_tis_nr; i++)
930 argv[argc++] = sel_tis[i]->filename;
931 } else {
932 for (i = 0; i < files_idx; i++)
933 argv[argc++] = av[i];
934 for (i = 0; i < sel_tis_nr; i++)
935 argv[argc++] = sel_tis[i]->filename;
936 for (i = files_idx; i < ac; i++)
937 argv[argc++] = av[i];
939 argv[argc] = NULL;
941 free(av);
943 for (i = 0; argv[i]; i++)
944 d_print("ARG: '%s'\n", argv[i]);
946 run = 1;
947 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
948 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
949 info_msg("Aborted");
950 run = 0;
953 if (run) {
954 int status;
956 if (spawn(argv, &status)) {
957 error_msg("executing %s: %s", argv[0], strerror(errno));
958 } else {
959 if (WIFEXITED(status)) {
960 int rc = WEXITSTATUS(status);
962 if (rc)
963 error_msg("%s returned %d", argv[0], rc);
965 if (WIFSIGNALED(status))
966 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
968 switch (cur_view) {
969 case TREE_VIEW:
970 case SORTED_VIEW:
971 /* this must be done before sel_tis are unreffed */
972 free_str_array(argv);
974 /* remove non-existed files, update tags for changed files */
975 cmus_update_tis(sel_tis, sel_tis_nr);
977 /* we don't own sel_tis anymore! */
978 return;
982 free_str_array(argv);
983 for (i = 0; sel_tis[i]; i++)
984 track_info_unref(sel_tis[i]);
985 free(sel_tis);
988 static int get_one_ti(void *data, struct track_info *ti)
990 struct track_info **sel_ti = data;
992 track_info_ref(ti);
993 *sel_ti = ti;
994 /* stop the for each loop, we need only the first selected track */
995 return 1;
998 static void cmd_echo(char *arg)
1000 struct track_info *sel_ti;
1001 char *ptr = arg;
1003 while (1) {
1004 ptr = strchr(ptr, '{');
1005 if (ptr == NULL)
1006 break;
1007 if (ptr[1] == '}')
1008 break;
1009 ptr++;
1012 if (ptr == NULL) {
1013 info_msg("%s", arg);
1014 return;
1017 if (cur_view > QUEUE_VIEW) {
1018 info_msg("echo with {} in its arguments is supported only in views 1-4");
1019 return;
1022 *ptr = 0;
1023 ptr += 2;
1025 /* get only the first selected track */
1026 sel_ti = NULL;
1028 editable_lock();
1029 switch (cur_view) {
1030 case TREE_VIEW:
1031 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1032 break;
1033 case SORTED_VIEW:
1034 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1035 break;
1036 case PLAYLIST_VIEW:
1037 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1038 break;
1039 case QUEUE_VIEW:
1040 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1041 break;
1043 editable_unlock();
1045 if (sel_ti == NULL)
1046 return;
1048 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1049 track_info_unref(sel_ti);
1052 #define VF_RELATIVE 0x01
1053 #define VF_PERCENTAGE 0x02
1055 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1057 unsigned int f = 0;
1058 int ch, val = 0, digits = 0, sign = 1;
1060 if (*arg == '-') {
1061 arg++;
1062 f |= VF_RELATIVE;
1063 sign = -1;
1064 } else if (*arg == '+') {
1065 arg++;
1066 f |= VF_RELATIVE;
1069 while (1) {
1070 ch = *arg++;
1071 if (ch < '0' || ch > '9')
1072 break;
1073 val *= 10;
1074 val += ch - '0';
1075 digits++;
1077 if (digits == 0)
1078 goto err;
1080 if (ch == '%') {
1081 f |= VF_PERCENTAGE;
1082 ch = *arg;
1084 if (ch)
1085 goto err;
1087 *value = sign * val;
1088 *flags = f;
1089 return 0;
1090 err:
1091 return -1;
1094 static int calc_vol(int val, int old, unsigned int flags)
1096 if (flags & VF_RELATIVE) {
1097 if (flags & VF_PERCENTAGE)
1098 val = scale_from_percentage(val, volume_max);
1099 val += old;
1100 } else if (flags & VF_PERCENTAGE) {
1101 val = scale_from_percentage(val, volume_max);
1103 return clamp(val, 0, volume_max);
1107 * :vol value [value]
1109 * where value is [-+]?[0-9]+%?
1111 static void cmd_vol(char *arg)
1113 char **values = get_words(arg);
1114 unsigned int lf, rf;
1115 int l, r, ol, or;
1117 if (values[1] && values[2])
1118 goto err;
1120 if (parse_vol_arg(values[0], &l, &lf))
1121 goto err;
1123 r = l;
1124 rf = lf;
1125 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1126 goto err;
1128 free_str_array(values);
1130 player_get_volume(&ol, &or);
1131 l = calc_vol(l, ol, lf);
1132 r = calc_vol(r, or, rf);
1133 player_set_volume(l, r);
1134 return;
1135 err:
1136 free_str_array(values);
1137 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1140 static void cmd_view(char *arg)
1142 int view;
1144 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view))
1145 set_view(view - 1);
1148 static void cmd_p_next(char *arg)
1150 cmus_next();
1153 static void cmd_p_pause(char *arg)
1155 player_pause();
1158 static void cmd_p_play(char *arg)
1160 if (arg) {
1161 player_play_file(arg);
1162 } else {
1163 player_play();
1167 static void cmd_p_prev(char *arg)
1169 cmus_prev();
1172 static void cmd_p_stop(char *arg)
1174 player_stop();
1177 static void cmd_search_next(char *arg)
1179 if (search_str) {
1180 if (!search_next(searchable, search_str, search_direction))
1181 search_not_found();
1185 static void cmd_search_prev(char *arg)
1187 if (search_str) {
1188 if (!search_next(searchable, search_str, !search_direction))
1189 search_not_found();
1193 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1195 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1198 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1200 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1203 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1205 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1208 static for_each_sel_ti_cb view_for_each_sel[4] = {
1209 tree_for_each_sel,
1210 sorted_for_each_sel,
1211 pl_for_each_sel,
1212 pq_for_each_sel
1215 /* wrapper for void lib_add_track(struct track_info *) etc. */
1216 static int wrapper_cb(void *data, struct track_info *ti)
1218 add_ti_cb add = data;
1220 add(ti);
1221 return 0;
1224 static void add_from_browser(add_ti_cb add, int job_type)
1226 char *sel = browser_get_sel();
1228 if (sel) {
1229 enum file_type ft;
1230 char *ret;
1232 ft = cmus_detect_ft(sel, &ret);
1233 if (ft != FILE_TYPE_INVALID) {
1234 cmus_add(add, ret, ft, job_type);
1235 window_down(browser_win, 1);
1237 free(ret);
1238 free(sel);
1242 static void cmd_win_add_l(char *arg)
1244 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1245 return;
1247 if (cur_view <= QUEUE_VIEW) {
1248 editable_lock();
1249 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1250 editable_unlock();
1251 } else if (cur_view == BROWSER_VIEW) {
1252 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1256 static void cmd_win_add_p(char *arg)
1258 /* could allow adding dups? */
1259 if (cur_view == PLAYLIST_VIEW)
1260 return;
1262 if (cur_view <= QUEUE_VIEW) {
1263 editable_lock();
1264 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1265 editable_unlock();
1266 } else if (cur_view == BROWSER_VIEW) {
1267 add_from_browser(pl_add_track, JOB_TYPE_PL);
1271 static void cmd_win_add_Q(char *arg)
1273 if (cur_view == QUEUE_VIEW)
1274 return;
1276 if (cur_view <= QUEUE_VIEW) {
1277 editable_lock();
1278 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1279 editable_unlock();
1280 } else if (cur_view == BROWSER_VIEW) {
1281 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1285 static void cmd_win_add_q(char *arg)
1287 if (cur_view == QUEUE_VIEW)
1288 return;
1290 if (cur_view <= QUEUE_VIEW) {
1291 editable_lock();
1292 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1293 editable_unlock();
1294 } else if (cur_view == BROWSER_VIEW) {
1295 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1299 static void cmd_win_activate(char *arg)
1301 struct track_info *info = NULL;
1303 editable_lock();
1304 switch (cur_view) {
1305 case TREE_VIEW:
1306 info = tree_set_selected();
1307 break;
1308 case SORTED_VIEW:
1309 info = sorted_set_selected();
1310 break;
1311 case PLAYLIST_VIEW:
1312 info = pl_set_selected();
1313 break;
1314 case QUEUE_VIEW:
1315 break;
1316 case BROWSER_VIEW:
1317 browser_enter();
1318 break;
1319 case FILTERS_VIEW:
1320 filters_activate();
1321 break;
1323 editable_unlock();
1325 if (info) {
1326 /* update lib/pl mode */
1327 if (cur_view < 2)
1328 play_library = 1;
1329 if (cur_view == 2)
1330 play_library = 0;
1332 player_play_file(info->filename);
1333 track_info_unref(info);
1337 static void cmd_win_mv_after(char *arg)
1339 editable_lock();
1340 switch (cur_view) {
1341 case TREE_VIEW:
1342 break;
1343 case SORTED_VIEW:
1344 editable_move_after(&lib_editable);
1345 break;
1346 case PLAYLIST_VIEW:
1347 editable_move_after(&pl_editable);
1348 break;
1349 case QUEUE_VIEW:
1350 editable_move_after(&pq_editable);
1351 break;
1352 case BROWSER_VIEW:
1353 break;
1354 case FILTERS_VIEW:
1355 break;
1357 editable_unlock();
1360 static void cmd_win_mv_before(char *arg)
1362 editable_lock();
1363 switch (cur_view) {
1364 case TREE_VIEW:
1365 break;
1366 case SORTED_VIEW:
1367 editable_move_before(&lib_editable);
1368 break;
1369 case PLAYLIST_VIEW:
1370 editable_move_before(&pl_editable);
1371 break;
1372 case QUEUE_VIEW:
1373 editable_move_before(&pq_editable);
1374 break;
1375 case BROWSER_VIEW:
1376 break;
1377 case FILTERS_VIEW:
1378 break;
1380 editable_unlock();
1383 static void cmd_win_remove(char *arg)
1385 editable_lock();
1386 switch (cur_view) {
1387 case TREE_VIEW:
1388 tree_remove_sel();
1389 break;
1390 case SORTED_VIEW:
1391 editable_remove_sel(&lib_editable);
1392 break;
1393 case PLAYLIST_VIEW:
1394 editable_remove_sel(&pl_editable);
1395 break;
1396 case QUEUE_VIEW:
1397 editable_remove_sel(&pq_editable);
1398 break;
1399 case BROWSER_VIEW:
1400 browser_delete();
1401 break;
1402 case FILTERS_VIEW:
1403 filters_delete_filter();
1404 break;
1406 editable_unlock();
1409 static void cmd_win_sel_cur(char *arg)
1411 editable_lock();
1412 switch (cur_view) {
1413 case TREE_VIEW:
1414 tree_sel_current();
1415 break;
1416 case SORTED_VIEW:
1417 sorted_sel_current();
1418 break;
1419 case PLAYLIST_VIEW:
1420 pl_sel_current();
1421 break;
1422 case QUEUE_VIEW:
1423 break;
1424 case BROWSER_VIEW:
1425 break;
1426 case FILTERS_VIEW:
1427 break;
1429 editable_unlock();
1432 static void cmd_win_toggle(char *arg)
1434 editable_lock();
1435 switch (cur_view) {
1436 case TREE_VIEW:
1437 tree_toggle_expand_artist();
1438 break;
1439 case SORTED_VIEW:
1440 editable_toggle_mark(&lib_editable);
1441 break;
1442 case PLAYLIST_VIEW:
1443 editable_toggle_mark(&pl_editable);
1444 break;
1445 case QUEUE_VIEW:
1446 editable_toggle_mark(&pq_editable);
1447 break;
1448 case BROWSER_VIEW:
1449 break;
1450 case FILTERS_VIEW:
1451 filters_toggle_filter();
1452 break;
1454 editable_unlock();
1457 static struct window *current_win(void)
1459 switch (cur_view) {
1460 case TREE_VIEW:
1461 return lib_cur_win;
1462 case SORTED_VIEW:
1463 return lib_editable.win;
1464 case PLAYLIST_VIEW:
1465 return pl_editable.win;
1466 case QUEUE_VIEW:
1467 return pq_editable.win;
1468 case BROWSER_VIEW:
1469 return browser_win;
1470 case FILTERS_VIEW:
1471 default:
1472 return filters_win;
1476 static void cmd_win_bottom(char *arg)
1478 editable_lock();
1479 window_goto_bottom(current_win());
1480 editable_unlock();
1483 static void cmd_win_down(char *arg)
1485 editable_lock();
1486 window_down(current_win(), 1);
1487 editable_unlock();
1490 static void cmd_win_next(char *arg)
1492 if (cur_view == TREE_VIEW) {
1493 editable_lock();
1494 tree_toggle_active_window();
1495 editable_unlock();
1499 static void cmd_win_pg_down(char *arg)
1501 editable_lock();
1502 window_page_down(current_win());
1503 editable_unlock();
1506 static void cmd_win_pg_up(char *arg)
1508 editable_lock();
1509 window_page_up(current_win());
1510 editable_unlock();
1513 static void cmd_win_top(char *arg)
1515 editable_lock();
1516 window_goto_top(current_win());
1517 editable_unlock();
1520 static void cmd_win_up(char *arg)
1522 editable_lock();
1523 window_up(current_win(), 1);
1524 editable_unlock();
1527 static void cmd_win_update(char *arg)
1529 switch (cur_view) {
1530 case TREE_VIEW:
1531 case SORTED_VIEW:
1532 cmus_update_lib();
1533 break;
1534 case BROWSER_VIEW:
1535 browser_reload();
1536 break;
1540 static void cmd_browser_up(char *arg)
1542 browser_up();
1545 static void cmd_refresh(char *arg)
1547 clearok(curscr, TRUE);
1548 refresh();
1551 /* tab exp {{{
1553 * these functions fill tabexp struct, which is resetted beforehand
1556 /* buffer used for tab expansion */
1557 static char expbuf[512];
1559 static int filter_directories(const char *name, const struct stat *s)
1561 return S_ISDIR(s->st_mode);
1564 static int filter_any(const char *name, const struct stat *s)
1566 return 1;
1569 static int filter_playable(const char *name, const struct stat *s)
1571 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1574 static int filter_playlist(const char *name, const struct stat *s)
1576 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1579 static int filter_supported(const char *name, const struct stat *s)
1581 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1584 static void expand_files(const char *str)
1586 expand_files_and_dirs(str, filter_any);
1589 static void expand_directories(const char *str)
1591 expand_files_and_dirs(str, filter_directories);
1594 static void expand_playable(const char *str)
1596 expand_files_and_dirs(str, filter_playable);
1599 static void expand_playlist(const char *str)
1601 expand_files_and_dirs(str, filter_playlist);
1604 static void expand_supported(const char *str)
1606 expand_files_and_dirs(str, filter_supported);
1609 static void expand_add(const char *str)
1611 int flag = parse_flags(&str, "lpqQ");
1613 if (flag == -1)
1614 return;
1615 if (str == NULL)
1616 str = "";
1617 expand_supported(str);
1619 if (tabexp.head && flag) {
1620 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1621 free(tabexp.head);
1622 tabexp.head = xstrdup(expbuf);
1626 static void expand_load_save(const char *str)
1628 int flag = parse_flags(&str, "lp");
1630 if (flag == -1)
1631 return;
1632 if (str == NULL)
1633 str = "";
1634 expand_playlist(str);
1636 if (tabexp.head && flag) {
1637 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1638 free(tabexp.head);
1639 tabexp.head = xstrdup(expbuf);
1643 static void expand_key_context(const char *str, const char *force)
1645 int pos, i, len = strlen(str);
1646 char **tails;
1648 tails = xnew(char *, NR_CTXS + 1);
1649 pos = 0;
1650 for (i = 0; key_context_names[i]; i++) {
1651 int cmp = strncmp(str, key_context_names[i], len);
1652 if (cmp > 0)
1653 continue;
1654 if (cmp < 0)
1655 break;
1656 tails[pos++] = xstrdup(key_context_names[i] + len);
1659 if (pos == 0) {
1660 free(tails);
1661 return;
1663 if (pos == 1) {
1664 char *tmp = xstrjoin(tails[0], " ");
1665 free(tails[0]);
1666 tails[0] = tmp;
1668 tails[pos] = NULL;
1669 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1670 tabexp.head = xstrdup(expbuf);
1671 tabexp.tails = tails;
1672 tabexp.nr_tails = pos;
1675 static int get_context(const char *str, int len)
1677 int i, c = -1, count = 0;
1679 for (i = 0; key_context_names[i]; i++) {
1680 if (strncmp(str, key_context_names[i], len) == 0) {
1681 if (key_context_names[i][len] == 0) {
1682 /* exact */
1683 return i;
1685 c = i;
1686 count++;
1689 if (count == 1)
1690 return c;
1691 return -1;
1694 static void expand_command_line(const char *str);
1696 static void expand_bind_args(const char *str)
1698 /* :bind context key function
1700 * possible values for str:
1702 * context k
1703 * context key f
1705 * you need to know context before you can expand function
1707 /* start and end pointers for context, key and function */
1708 const char *cs, *ce, *ks, *ke, *fs;
1709 char *tmp, **tails;
1710 int i, c, k, len, pos, alloc, count;
1711 int flag = parse_flags((const char **)&str, "f");
1712 const char *force = "";
1714 if (flag == -1)
1715 return;
1716 if (str == NULL)
1717 str = "";
1719 if (flag == 'f')
1720 force = "-f ";
1722 cs = str;
1723 ce = strchr(cs, ' ');
1724 if (ce == NULL) {
1725 expand_key_context(cs, force);
1726 return;
1729 /* context must be expandable */
1730 c = get_context(cs, ce - cs);
1731 if (c == -1) {
1732 /* context is ambiguous or invalid */
1733 return;
1736 ks = ce;
1737 while (*ks == ' ')
1738 ks++;
1739 ke = strchr(ks, ' ');
1740 if (ke == NULL) {
1741 /* expand key */
1742 len = strlen(ks);
1743 tails = NULL;
1744 alloc = 0;
1745 pos = 0;
1746 for (i = 0; key_table[i].name; i++) {
1747 int cmp = strncmp(ks, key_table[i].name, len);
1748 if (cmp > 0)
1749 continue;
1750 if (cmp < 0)
1751 break;
1752 tails = str_array_add(tails, &alloc, &pos, xstrdup(key_table[i].name + len));
1755 if (pos == 0) {
1756 return;
1758 if (pos == 1) {
1759 tmp = xstrjoin(tails[0], " ");
1760 free(tails[0]);
1761 tails[0] = tmp;
1764 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
1766 tails[pos] = NULL;
1767 tabexp.head = xstrdup(expbuf);
1768 tabexp.tails = tails;
1769 tabexp.nr_tails = pos;
1770 return;
1773 /* key must be expandable */
1774 k = -1;
1775 count = 0;
1776 for (i = 0; key_table[i].name; i++) {
1777 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
1778 if (key_table[i].name[ke - ks] == 0) {
1779 /* exact */
1780 k = i;
1781 count = 1;
1782 break;
1784 k = i;
1785 count++;
1788 if (count != 1) {
1789 /* key is ambiguous or invalid */
1790 return;
1793 fs = ke;
1794 while (*fs == ' ')
1795 fs++;
1797 if (*fs == ':')
1798 fs++;
1800 /* expand com [arg...] */
1801 expand_command_line(fs);
1802 if (tabexp.head == NULL) {
1803 /* command expand failed */
1804 return;
1808 * tabexp.head is now "com"
1809 * tabexp.tails is [ mand1 mand2 ... ]
1811 * need to change tabexp.head to "context key com"
1814 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
1815 key_table[k].name, tabexp.head);
1816 free(tabexp.head);
1817 tabexp.head = xstrdup(expbuf);
1820 static void expand_unbind_args(const char *str)
1822 /* :unbind context key */
1823 /* start and end pointers for context and key */
1824 const char *cs, *ce, *ks;
1825 char **tails;
1826 int c, len, pos, alloc;
1827 const struct binding *b;
1829 cs = str;
1830 ce = strchr(cs, ' ');
1831 if (ce == NULL) {
1832 expand_key_context(cs, "");
1833 return;
1836 /* context must be expandable */
1837 c = get_context(cs, ce - cs);
1838 if (c == -1) {
1839 /* context is ambiguous or invalid */
1840 return;
1843 ks = ce;
1844 while (*ks == ' ')
1845 ks++;
1847 /* expand key */
1848 len = strlen(ks);
1849 tails = NULL;
1850 alloc = 0;
1851 pos = 0;
1852 b = key_bindings[c];
1853 while (b) {
1854 if (strncmp(ks, b->key->name, len) == 0)
1855 tails = str_array_add(tails, &alloc, &pos, xstrdup(b->key->name + len));
1856 b = b->next;
1858 if (pos == 0)
1859 return;
1861 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
1863 tails[pos] = NULL;
1864 tabexp.head = xstrdup(expbuf);
1865 tabexp.tails = tails;
1866 tabexp.nr_tails = pos;
1869 static void expand_factivate(const char *str)
1871 /* "name1 name2 name3", expand only name3 */
1872 struct filter_entry *e;
1873 int str_len, len, i, pos, alloc;
1874 const char *name;
1875 char **tails;
1877 str_len = strlen(str);
1878 i = str_len;
1879 while (i > 0) {
1880 if (str[i - 1] == ' ')
1881 break;
1882 i--;
1884 len = str_len - i;
1885 name = str + i;
1887 tails = NULL;
1888 alloc = 0;
1889 pos = 0;
1890 list_for_each_entry(e, &filters_head, node) {
1891 if (strncmp(name, e->name, len) == 0)
1892 tails = str_array_add(tails, &alloc, &pos, xstrdup(e->name + len));
1894 if (pos == 0)
1895 return;
1897 tails[pos] = NULL;
1898 tabexp.head = xstrdup(str);
1899 tabexp.tails = tails;
1900 tabexp.nr_tails = pos;
1903 static void expand_options(const char *str)
1905 struct cmus_opt *opt;
1906 int len;
1907 char **tails;
1909 /* tabexp is resetted */
1910 len = strlen(str);
1911 if (len > 1 && str[len - 1] == '=') {
1912 /* expand value */
1913 char *var = xstrndup(str, len - 1);
1915 list_for_each_entry(opt, &option_head, node) {
1916 if (strcmp(var, opt->name) == 0) {
1917 char buf[OPTION_MAX_SIZE];
1919 tails = xnew(char *, 1);
1921 buf[0] = 0;
1922 opt->get(opt->id, buf);
1923 tails[0] = xstrdup(buf);
1925 tails[1] = NULL;
1926 tabexp.head = xstrdup(str);
1927 tabexp.tails = tails;
1928 tabexp.nr_tails = 1;
1929 free(var);
1930 return;
1933 free(var);
1934 } else {
1935 /* expand variable */
1936 int pos;
1938 tails = xnew(char *, nr_options + 1);
1939 pos = 0;
1940 list_for_each_entry(opt, &option_head, node) {
1941 if (strncmp(str, opt->name, len) == 0)
1942 tails[pos++] = xstrdup(opt->name + len);
1944 if (pos > 0) {
1945 if (pos == 1) {
1946 /* only one variable matches, add '=' */
1947 char *tmp = xstrjoin(tails[0], "=");
1949 free(tails[0]);
1950 tails[0] = tmp;
1953 tails[pos] = NULL;
1954 tabexp.head = xstrdup(str);
1955 tabexp.tails = tails;
1956 tabexp.nr_tails = pos;
1957 } else {
1958 free(tails);
1963 static void expand_toptions(const char *str)
1965 struct cmus_opt *opt;
1966 int len, pos;
1967 char **tails;
1969 tails = xnew(char *, nr_options + 1);
1970 len = strlen(str);
1971 pos = 0;
1972 list_for_each_entry(opt, &option_head, node) {
1973 if (opt->toggle == NULL)
1974 continue;
1975 if (strncmp(str, opt->name, len) == 0)
1976 tails[pos++] = xstrdup(opt->name + len);
1978 if (pos > 0) {
1979 tails[pos] = NULL;
1980 tabexp.head = xstrdup(str);
1981 tabexp.tails = tails;
1982 tabexp.nr_tails = pos;
1983 } else {
1984 free(tails);
1988 static void load_themes(const char *dir, const char *str, int *allocp)
1990 DIR *d;
1991 struct dirent *dirent;
1992 int len = strlen(str);
1994 d = opendir(dir);
1995 if (d == NULL)
1996 return;
1998 while ((dirent = readdir(d)) != NULL) {
1999 const char *dot, *name = dirent->d_name;
2000 char filename[512];
2001 struct stat s;
2003 if (strncmp(name, str, len))
2004 continue;
2006 dot = strrchr(name, '.');
2007 if (dot == NULL || strcmp(dot, ".theme"))
2008 continue;
2010 if (dot - name < len) {
2011 /* str is "foo.th"
2012 * matches "foo.theme"
2013 * which also ends with ".theme"
2015 continue;
2018 snprintf(filename, sizeof(filename), "%s/%s", dir, name);
2019 if (stat(filename, &s) || !S_ISREG(s.st_mode))
2020 continue;
2022 tabexp.tails = str_array_add(tabexp.tails, allocp, &tabexp.nr_tails,
2023 xstrndup(name + len, dot - name - len));
2025 closedir(d);
2028 static void expand_colorscheme(const char *str)
2030 int alloc = 0;
2032 tabexp.nr_tails = 0;
2034 load_themes(cmus_config_dir, str, &alloc);
2035 load_themes(DATADIR "/cmus", str, &alloc);
2037 if (alloc) {
2038 qsort(tabexp.tails, tabexp.nr_tails, sizeof(char *), strptrcmp);
2040 tabexp.tails[tabexp.nr_tails] = NULL;
2041 tabexp.head = xstrdup(str);
2045 /* tab exp }}} */
2047 struct command {
2048 const char *name;
2049 void (*func)(char *arg);
2051 /* min/max number of arguments */
2052 int min_args;
2053 int max_args;
2055 void (*expand)(const char *str);
2058 /* sort by name */
2059 static struct command commands[] = {
2060 { "add", cmd_add, 1, 1, expand_add },
2061 { "bind", cmd_bind, 1, 1, expand_bind_args },
2062 { "browser-up", cmd_browser_up, 0, 0, NULL },
2063 { "cd", cmd_cd, 0, 1, expand_directories},
2064 { "clear", cmd_clear, 0, 1, NULL },
2065 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme},
2066 { "echo", cmd_echo, 1,-1, NULL },
2067 { "factivate", cmd_factivate, 0, 1, expand_factivate },
2068 { "filter", cmd_filter, 0, 1, NULL },
2069 { "fset", cmd_fset, 1, 1, NULL },
2070 { "invert", cmd_invert, 0, 0, NULL },
2071 { "load", cmd_load, 1, 1, expand_load_save },
2072 { "mark", cmd_mark, 0, 1, NULL },
2073 { "player-next", cmd_p_next, 0, 0, NULL },
2074 { "player-pause", cmd_p_pause, 0, 0, NULL },
2075 { "player-play", cmd_p_play, 0, 1, expand_playable },
2076 { "player-prev", cmd_p_prev, 0, 0, NULL },
2077 { "player-stop", cmd_p_stop, 0, 0, NULL },
2078 { "quit", cmd_quit, 0, 0, NULL },
2079 { "refresh", cmd_refresh, 0, 0, NULL },
2080 { "run", cmd_run, 1,-1, NULL },
2081 { "save", cmd_save, 0, 1, expand_load_save },
2082 { "search-next", cmd_search_next,0, 0, NULL },
2083 { "search-prev", cmd_search_prev,0, 0, NULL },
2084 { "seek", cmd_seek, 1, 1, NULL },
2085 { "set", cmd_set, 1, 1, expand_options },
2086 { "showbind", cmd_showbind, 1, 1, expand_unbind_args},
2087 { "shuffle", cmd_reshuffle, 0, 0, NULL },
2088 { "source", cmd_source, 1, 1, expand_files },
2089 { "toggle", cmd_toggle, 1, 1, expand_toptions },
2090 { "unbind", cmd_unbind, 1, 1, expand_unbind_args},
2091 { "unmark", cmd_unmark, 0, 0, NULL },
2092 { "view", cmd_view, 1, 1, NULL },
2093 { "vol", cmd_vol, 1, 2, NULL },
2094 { "win-activate", cmd_win_activate,0, 0, NULL },
2095 { "win-add-l", cmd_win_add_l, 0, 0, NULL },
2096 { "win-add-p", cmd_win_add_p, 0, 0, NULL },
2097 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL },
2098 { "win-add-q", cmd_win_add_q, 0, 0, NULL },
2099 { "win-bottom", cmd_win_bottom, 0, 0, NULL },
2100 { "win-down", cmd_win_down, 0, 0, NULL },
2101 { "win-mv-after", cmd_win_mv_after,0, 0, NULL },
2102 { "win-mv-before", cmd_win_mv_before,0, 0, NULL },
2103 { "win-next", cmd_win_next, 0, 0, NULL },
2104 { "win-page-down", cmd_win_pg_down,0, 0, NULL },
2105 { "win-page-up", cmd_win_pg_up, 0, 0, NULL },
2106 { "win-remove", cmd_win_remove, 0, 0, NULL },
2107 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL },
2108 { "win-toggle", cmd_win_toggle, 0, 0, NULL },
2109 { "win-top", cmd_win_top, 0, 0, NULL },
2110 { "win-up", cmd_win_up, 0, 0, NULL },
2111 { "win-update", cmd_win_update, 0, 0, NULL },
2112 { NULL, NULL, 0, 0, 0 }
2115 /* fills tabexp struct */
2116 static void expand_commands(const char *str)
2118 int i, len, pos;
2119 char **tails;
2121 /* tabexp is resetted */
2122 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2123 len = strlen(str);
2124 pos = 0;
2125 for (i = 0; commands[i].name; i++) {
2126 if (strncmp(str, commands[i].name, len) == 0)
2127 tails[pos++] = xstrdup(commands[i].name + len);
2129 if (pos > 0) {
2130 if (pos == 1) {
2131 /* only one command matches, add ' ' */
2132 char *tmp = xstrjoin(tails[0], " ");
2134 free(tails[0]);
2135 tails[0] = tmp;
2137 tails[pos] = NULL;
2138 tabexp.head = xstrdup(str);
2139 tabexp.tails = tails;
2140 tabexp.nr_tails = pos;
2141 } else {
2142 free(tails);
2146 static const struct command *get_command(const char *str, int len)
2148 int i;
2150 for (i = 0; commands[i].name; i++) {
2151 if (strncmp(str, commands[i].name, len))
2152 continue;
2154 if (commands[i].name[len] == 0) {
2155 /* exact */
2156 return &commands[i];
2159 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2160 /* ambiguous */
2161 return NULL;
2163 return &commands[i];
2165 return NULL;
2168 /* fills tabexp struct */
2169 static void expand_command_line(const char *str)
2171 /* :command [arg]...
2173 * examples:
2175 * str expanded value (tabexp.head)
2176 * -------------------------------------
2177 * fs fset
2178 * b c bind common
2179 * se se (tabexp.tails = [ ek t ])
2181 /* command start/end, argument start */
2182 const char *cs, *ce, *as;
2183 const struct command *cmd;
2185 cs = str;
2186 ce = strchr(cs, ' ');
2187 if (ce == NULL) {
2188 /* expand command */
2189 expand_commands(cs);
2190 return;
2193 /* command must be expandable */
2194 cmd = get_command(cs, ce - cs);
2195 if (cmd == NULL) {
2196 /* command ambiguous or invalid */
2197 return;
2200 if (cmd->expand == NULL) {
2201 /* can't expand argument */
2202 return;
2205 as = ce;
2206 while (*as == ' ')
2207 as++;
2209 /* expand argument */
2210 cmd->expand(as);
2211 if (tabexp.head == NULL) {
2212 /* argument expansion failed */
2213 return;
2216 /* tabexp.head is now start of the argument string */
2217 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2218 free(tabexp.head);
2219 tabexp.head = xstrdup(expbuf);
2222 static void tab_expand(void)
2224 char *s1, *s2, *tmp;
2225 int pos;
2227 /* strip white space */
2228 pos = 0;
2229 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2230 pos++;
2232 /* string to expand */
2233 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2235 /* tail */
2236 s2 = xstrdup(cmdline.line + cmdline.bpos);
2238 tmp = tabexp_expand(s1, expand_command_line);
2239 if (tmp) {
2240 /* tmp.s2 */
2241 int l1, l2;
2243 l1 = strlen(tmp);
2244 l2 = strlen(s2);
2245 cmdline.blen = l1 + l2;
2246 if (cmdline.blen >= cmdline.size) {
2247 while (cmdline.blen >= cmdline.size)
2248 cmdline.size *= 2;
2249 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2251 sprintf(cmdline.line, "%s%s", tmp, s2);
2252 cmdline.bpos = l1;
2253 cmdline.cpos = u_strlen(tmp);
2254 cmdline.clen = u_strlen(cmdline.line);
2255 free(tmp);
2257 free(s1);
2258 free(s2);
2261 static void reset_tab_expansion(void)
2263 tabexp_reset();
2264 arg_expand_cmd = -1;
2267 /* FIXME: parse all arguments */
2268 void run_command(const char *buf)
2270 char *cmd, *arg;
2271 int cmd_start, cmd_end, cmd_len;
2272 int arg_start, arg_end;
2273 int i;
2275 i = 0;
2276 while (buf[i] && buf[i] == ' ')
2277 i++;
2279 if (buf[i] == '#')
2280 return;
2282 cmd_start = i;
2283 while (buf[i] && buf[i] != ' ')
2284 i++;
2285 cmd_end = i;
2286 while (buf[i] && buf[i] == ' ')
2287 i++;
2288 arg_start = i;
2289 while (buf[i])
2290 i++;
2291 arg_end = i;
2293 cmd_len = cmd_end - cmd_start;
2294 if (cmd_len == 0)
2295 return;
2297 cmd = xstrndup(buf + cmd_start, cmd_len);
2298 if (arg_start == arg_end) {
2299 arg = NULL;
2300 } else {
2301 arg = xstrndup(buf + arg_start, arg_end - arg_start);
2303 i = 0;
2304 while (1) {
2305 if (commands[i].name == NULL) {
2306 error_msg("unknown command\n");
2307 break;
2309 if (strncmp(cmd, commands[i].name, cmd_len) == 0) {
2310 const char *next = commands[i + 1].name;
2311 int exact = commands[i].name[cmd_len] == 0;
2313 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2314 error_msg("ambiguous command\n");
2315 break;
2317 if (commands[i].min_args > 0 && arg == NULL) {
2318 error_msg("not enough arguments\n");
2319 break;
2321 if (commands[i].max_args == 0 && arg) {
2322 error_msg("too many arguments\n");
2323 break;
2325 commands[i].func(arg);
2326 break;
2328 i++;
2330 free(arg);
2331 free(cmd);
2334 static void reset_history_search(void)
2336 history_reset_search(&cmd_history);
2337 free(history_search_text);
2338 history_search_text = NULL;
2341 static void backspace(void)
2343 if (cmdline.clen > 0) {
2344 cmdline_backspace();
2345 } else {
2346 input_mode = NORMAL_MODE;
2350 void command_mode_ch(uchar ch)
2352 switch (ch) {
2353 case 0x01: // ^A
2354 cmdline_move_home();
2355 break;
2356 case 0x02: // ^B
2357 cmdline_move_left();
2358 break;
2359 case 0x04: // ^D
2360 cmdline_delete_ch();
2361 break;
2362 case 0x05: // ^E
2363 cmdline_move_end();
2364 break;
2365 case 0x06: // ^F
2366 cmdline_move_right();
2367 break;
2368 case 0x03: // ^C
2369 case 0x07: // ^G
2370 case 0x1B: // ESC
2371 if (cmdline.blen) {
2372 history_add_line(&cmd_history, cmdline.line);
2373 cmdline_clear();
2375 input_mode = NORMAL_MODE;
2376 break;
2377 case 0x0A:
2378 if (cmdline.blen) {
2379 run_command(cmdline.line);
2380 history_add_line(&cmd_history, cmdline.line);
2381 cmdline_clear();
2383 input_mode = NORMAL_MODE;
2384 break;
2385 case 0x0B:
2386 cmdline_clear_end();
2387 break;
2388 case 0x09:
2389 /* tab expansion should not complain */
2390 display_errors = 0;
2392 tab_expand();
2393 break;
2394 case 0x15:
2395 cmdline_backspace_to_bol();
2396 break;
2397 case 127:
2398 backspace();
2399 break;
2400 default:
2401 cmdline_insert_ch(ch);
2403 reset_history_search();
2404 if (ch != 0x09)
2405 reset_tab_expansion();
2408 void command_mode_key(int key)
2410 reset_tab_expansion();
2411 switch (key) {
2412 case KEY_DC:
2413 cmdline_delete_ch();
2414 break;
2415 case KEY_BACKSPACE:
2416 backspace();
2417 break;
2418 case KEY_LEFT:
2419 cmdline_move_left();
2420 return;
2421 case KEY_RIGHT:
2422 cmdline_move_right();
2423 return;
2424 case KEY_HOME:
2425 cmdline_move_home();
2426 return;
2427 case KEY_END:
2428 cmdline_move_end();
2429 return;
2430 case KEY_UP:
2432 const char *s;
2434 if (history_search_text == NULL)
2435 history_search_text = xstrdup(cmdline.line);
2436 s = history_search_forward(&cmd_history, history_search_text);
2437 if (s)
2438 cmdline_set_text(s);
2440 return;
2441 case KEY_DOWN:
2442 if (history_search_text) {
2443 const char *s;
2445 s = history_search_backward(&cmd_history, history_search_text);
2446 if (s) {
2447 cmdline_set_text(s);
2448 } else {
2449 cmdline_set_text(history_search_text);
2452 return;
2453 default:
2454 d_print("key = %c (%d)\n", key, key);
2456 reset_history_search();
2459 void commands_init(void)
2461 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2462 history_load(&cmd_history, cmd_history_filename, 2000);
2465 void commands_exit(void)
2467 history_save(&cmd_history);
2468 free(cmd_history_filename);
2469 tabexp_reset();