Add simple authentication support for cmus-remote
[cmus.git] / command_mode.c
blob9626d24c6f14be9f974bd873193ec59d7ff582f3
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "command_mode.h"
21 #include "search_mode.h"
22 #include "cmdline.h"
23 #include "options.h"
24 #include "ui_curses.h"
25 #include "history.h"
26 #include "tabexp.h"
27 #include "tabexp_file.h"
28 #include "browser.h"
29 #include "filters.h"
30 #include "player.h"
31 #include "editable.h"
32 #include "lib.h"
33 #include "pl.h"
34 #include "play_queue.h"
35 #include "cmus.h"
36 #include "worker.h"
37 #include "keys.h"
38 #include "xmalloc.h"
39 #include "xstrjoin.h"
40 #include "misc.h"
41 #include "path.h"
42 #include "format_print.h"
43 #include "spawn.h"
44 #include "utils.h"
45 #include "list.h"
46 #include "debug.h"
47 #include "load_dir.h"
48 #include "config/datadir.h"
49 #include "help.h"
51 #include <stdlib.h>
52 #include <ctype.h>
53 #include <sys/types.h>
54 #include <sys/wait.h>
55 #include <dirent.h>
56 #include <pwd.h>
58 #if defined(__sun__)
59 #include <ncurses.h>
60 #else
61 #include <curses.h>
62 #endif
64 static struct history cmd_history;
65 static char *cmd_history_filename;
66 static char *history_search_text = NULL;
67 static int arg_expand_cmd = -1;
69 static char *get_home_dir(const char *username)
71 struct passwd *passwd;
73 if (username == NULL)
74 return xstrdup(home_dir);
75 passwd = getpwnam(username);
76 if (passwd == NULL)
77 return NULL;
78 /* don't free passwd */
79 return xstrdup(passwd->pw_dir);
82 static char *expand_filename(const char *name)
84 if (name[0] == '~') {
85 char *slash;
87 slash = strchr(name, '/');
88 if (slash) {
89 char *username, *home;
91 if (slash - name - 1 > 0) {
92 /* ~user/... */
93 username = xstrndup(name + 1, slash - name - 1);
94 } else {
95 /* ~/... */
96 username = NULL;
98 home = get_home_dir(username);
99 free(username);
100 if (home) {
101 char *expanded;
103 expanded = xstrjoin(home, slash);
104 free(home);
105 return expanded;
106 } else {
107 return xstrdup(name);
109 } else {
110 if (name[1] == 0) {
111 return xstrdup(home_dir);
112 } else {
113 char *home;
115 home = get_home_dir(name + 1);
116 if (home)
117 return home;
118 return xstrdup(name);
121 } else {
122 return xstrdup(name);
126 /* view {{{ */
128 void view_clear(int view)
130 switch (view) {
131 case TREE_VIEW:
132 case SORTED_VIEW:
133 worker_remove_jobs(JOB_TYPE_LIB);
134 editable_lock();
135 editable_clear(&lib_editable);
137 /* FIXME: make this optional? */
138 lib_clear_store();
140 editable_unlock();
141 break;
142 case PLAYLIST_VIEW:
143 worker_remove_jobs(JOB_TYPE_PL);
144 editable_lock();
145 editable_clear(&pl_editable);
146 editable_unlock();
147 break;
148 case QUEUE_VIEW:
149 worker_remove_jobs(JOB_TYPE_QUEUE);
150 editable_lock();
151 editable_clear(&pq_editable);
152 editable_unlock();
153 break;
154 default:
155 info_msg(":clear only works in views 1-4");
159 void view_add(int view, char *arg, int prepend)
161 char *tmp, *name;
162 enum file_type ft;
164 tmp = expand_filename(arg);
165 ft = cmus_detect_ft(tmp, &name);
166 if (ft == FILE_TYPE_INVALID) {
167 error_msg("adding '%s': %s", tmp, strerror(errno));
168 free(tmp);
169 return;
171 free(tmp);
173 switch (view) {
174 case TREE_VIEW:
175 case SORTED_VIEW:
176 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
177 break;
178 case PLAYLIST_VIEW:
179 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
180 break;
181 case QUEUE_VIEW:
182 if (prepend) {
183 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
184 } else {
185 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
187 break;
188 default:
189 info_msg(":add only works in views 1-4");
191 free(name);
194 void view_load(int view, char *arg)
196 char *tmp, *name;
197 enum file_type ft;
199 tmp = expand_filename(arg);
200 ft = cmus_detect_ft(tmp, &name);
201 if (ft == FILE_TYPE_INVALID) {
202 error_msg("loading '%s': %s", tmp, strerror(errno));
203 free(tmp);
204 return;
206 free(tmp);
208 if (ft == FILE_TYPE_FILE)
209 ft = FILE_TYPE_PL;
210 if (ft != FILE_TYPE_PL) {
211 error_msg("loading '%s': not a playlist file", name);
212 free(name);
213 return;
216 switch (view) {
217 case TREE_VIEW:
218 case SORTED_VIEW:
219 worker_remove_jobs(JOB_TYPE_LIB);
220 editable_lock();
221 editable_clear(&lib_editable);
222 editable_unlock();
223 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
224 free(lib_filename);
225 lib_filename = name;
226 break;
227 case PLAYLIST_VIEW:
228 worker_remove_jobs(JOB_TYPE_PL);
229 editable_lock();
230 editable_clear(&pl_editable);
231 editable_unlock();
232 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
233 free(pl_filename);
234 pl_filename = name;
235 break;
236 default:
237 info_msg(":load only works in views 1-3");
238 free(name);
242 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
244 char *filename = *filenamep;
246 if (arg) {
247 free(filename);
248 filename = xstrdup(arg);
249 *filenamep = filename;
252 editable_lock();
253 if (cmus_save(for_each_ti, filename) == -1)
254 error_msg("saving '%s': %s", filename, strerror(errno));
255 editable_unlock();
258 void view_save(int view, char *arg)
260 if (arg) {
261 char *tmp;
263 tmp = expand_filename(arg);
264 arg = path_absolute(tmp);
265 free(tmp);
268 switch (view) {
269 case TREE_VIEW:
270 case SORTED_VIEW:
271 do_save(lib_for_each, arg, &lib_filename);
272 break;
273 case PLAYLIST_VIEW:
274 do_save(pl_for_each, arg, &pl_filename);
275 break;
276 default:
277 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
279 free(arg);
282 /* }}} */
284 /* only returns the last flag which is enough for it's callers */
285 static int parse_flags(const char **strp, const char *flags)
287 const char *str = *strp;
288 int flag = 0;
290 if (str == NULL)
291 return flag;
293 while (*str) {
294 if (*str != '-')
295 break;
297 // "-"
298 if (str[1] == 0)
299 break;
301 // "--" or "-- "
302 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
303 str += 2;
304 break;
307 // not "-?" or "-? "
308 if (str[2] && str[2] != ' ')
309 break;
311 flag = str[1];
312 if (!strchr(flags, flag)) {
313 error_msg("invalid option -%c", flag);
314 return -1;
317 str += 2;
319 while (*str == ' ')
320 str++;
322 while (*str == ' ')
323 str++;
324 if (*str == 0)
325 str = NULL;
326 *strp = str;
327 return flag;
330 static int flag_to_view(int flag)
332 switch (flag) {
333 case 'l':
334 return TREE_VIEW;
335 case 'p':
336 return PLAYLIST_VIEW;
337 case 'q':
338 case 'Q':
339 return QUEUE_VIEW;
340 default:
341 return cur_view;
345 static void cmd_add(char *arg)
347 int flag = parse_flags((const char **)&arg, "lpqQ");
349 if (flag == -1)
350 return;
351 if (arg == NULL) {
352 error_msg("not enough arguments\n");
353 return;
355 view_add(flag_to_view(flag), arg, flag == 'Q');
358 static void cmd_clear(char *arg)
360 int flag = parse_flags((const char **)&arg, "lpq");
362 if (flag == -1)
363 return;
364 if (arg) {
365 error_msg("too many arguments\n");
366 return;
368 view_clear(flag_to_view(flag));
371 static void cmd_load(char *arg)
373 int flag = parse_flags((const char **)&arg, "lp");
375 if (flag == -1)
376 return;
377 if (arg == NULL) {
378 error_msg("not enough arguments\n");
379 return;
381 view_load(flag_to_view(flag), arg);
384 static void cmd_save(char *arg)
386 int flag = parse_flags((const char **)&arg, "lp");
388 if (flag == -1)
389 return;
390 view_save(flag_to_view(flag), arg);
393 static void cmd_set(char *arg)
395 char *value = NULL;
396 int i;
398 for (i = 0; arg[i]; i++) {
399 if (arg[i] == '=') {
400 arg[i] = 0;
401 value = &arg[i + 1];
402 break;
405 if (value) {
406 option_set(arg, value);
407 help_win->changed = 1;
408 } else {
409 struct cmus_opt *opt;
410 char buf[OPTION_MAX_SIZE];
412 /* support "set <option>?" */
413 i--;
414 if (arg[i] == '?')
415 arg[i] = 0;
417 opt = option_find(arg);
418 if (opt) {
419 opt->get(opt->id, buf);
420 info_msg("setting: '%s=%s'", arg, buf);
425 static void cmd_toggle(char *arg)
427 struct cmus_opt *opt = option_find(arg);
429 if (opt == NULL)
430 return;
432 if (opt->toggle == NULL) {
433 error_msg("%s is not toggle option", opt->name);
434 return;
436 opt->toggle(opt->id);
437 help_win->changed = 1;
440 static int get_number(char *str, char **end)
442 int val = 0;
444 while (*str >= '0' && *str <= '9') {
445 val *= 10;
446 val += *str++ - '0';
448 *end = str;
449 return val;
452 static void cmd_seek(char *arg)
454 int relative = 0;
455 int seek = 0, sign = 1, count;
457 switch (*arg) {
458 case '-':
459 sign = -1;
460 case '+':
461 relative = 1;
462 arg++;
463 break;
466 count = 0;
467 goto inside;
469 do {
470 int num;
471 char *end;
473 if (*arg != ':')
474 break;
475 arg++;
476 inside:
477 num = get_number(arg, &end);
478 if (arg == end)
479 break;
480 arg = end;
481 seek = seek * 60 + num;
482 } while (++count < 3);
484 seek *= sign;
485 if (!count)
486 goto err;
488 if (count == 1) {
489 switch (tolower(*arg)) {
490 case 'h':
491 seek *= 60;
492 case 'm':
493 seek *= 60;
494 case 's':
495 arg++;
496 break;
500 if (!*arg) {
501 player_seek(seek, relative);
502 return;
504 err:
505 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
508 static void cmd_factivate(char *arg)
510 editable_lock();
511 filters_activate_names(arg);
512 editable_unlock();
515 static void cmd_filter(char *arg)
517 editable_lock();
518 filters_set_anonymous(arg);
519 editable_unlock();
522 static void cmd_fset(char *arg)
524 filters_set_filter(arg);
527 static void cmd_invert(char *arg)
529 editable_lock();
530 switch (cur_view) {
531 case SORTED_VIEW:
532 editable_invert_marks(&lib_editable);
533 break;
534 case PLAYLIST_VIEW:
535 editable_invert_marks(&pl_editable);
536 break;
537 case QUEUE_VIEW:
538 editable_invert_marks(&pq_editable);
539 break;
540 default:
541 info_msg(":invert only works in views 2-4");
543 editable_unlock();
546 static void cmd_mark(char *arg)
548 editable_lock();
549 switch (cur_view) {
550 case SORTED_VIEW:
551 editable_mark(&lib_editable, arg);
552 break;
553 case PLAYLIST_VIEW:
554 editable_mark(&pl_editable, arg);
555 break;
556 case QUEUE_VIEW:
557 editable_mark(&pq_editable, arg);
558 break;
559 default:
560 info_msg(":mark only works in views 2-4");
562 editable_unlock();
565 static void cmd_unmark(char *arg)
567 editable_lock();
568 switch (cur_view) {
569 case SORTED_VIEW:
570 editable_unmark(&lib_editable);
571 break;
572 case PLAYLIST_VIEW:
573 editable_unmark(&pl_editable);
574 break;
575 case QUEUE_VIEW:
576 editable_unmark(&pq_editable);
577 break;
578 default:
579 info_msg(":unmark only works in views 2-4");
581 editable_unlock();
584 static void cmd_cd(char *arg)
586 if (arg) {
587 char *dir, *absolute;
589 dir = expand_filename(arg);
590 absolute = path_absolute(dir);
591 if (chdir(dir) == -1) {
592 error_msg("could not cd to '%s': %s", dir, strerror(errno));
593 } else {
594 browser_chdir(absolute);
596 free(absolute);
597 free(dir);
598 } else {
599 if (chdir(home_dir) == -1) {
600 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
601 } else {
602 browser_chdir(home_dir);
607 static void cmd_bind(char *arg)
609 int flag = parse_flags((const char **)&arg, "f");
610 char *key, *func;
612 if (flag == -1)
613 return;
615 if (arg == NULL)
616 goto err;
618 key = strchr(arg, ' ');
619 if (key == NULL)
620 goto err;
621 *key++ = 0;
622 while (*key == ' ')
623 key++;
625 func = strchr(key, ' ');
626 if (func == NULL)
627 goto err;
628 *func++ = 0;
629 while (*func == ' ')
630 func++;
631 if (*func == 0)
632 goto err;
634 key_bind(arg, key, func, flag == 'f');
635 return;
636 err:
637 error_msg("expecting 3 arguments (context, key and function)\n");
640 static void cmd_unbind(char *arg)
642 int flag = parse_flags((const char **)&arg, "f");
643 char *key;
645 if (flag == -1)
646 return;
648 if (arg == NULL)
649 goto err;
651 key = strchr(arg, ' ');
652 if (key == NULL)
653 goto err;
654 *key++ = 0;
655 while (*key == ' ')
656 key++;
657 if (*key == 0)
658 goto err;
660 /* FIXME: remove spaces at end */
662 key_unbind(arg, key, flag == 'f');
663 return;
664 err:
665 error_msg("expecting 2 arguments (context and key)\n");
668 static void cmd_showbind(char *arg)
670 char *key;
672 key = strchr(arg, ' ');
673 if (key == NULL)
674 goto err;
675 *key++ = 0;
676 while (*key == ' ')
677 key++;
678 if (*key == 0)
679 goto err;
681 /* FIXME: remove spaces at end */
683 show_binding(arg, key);
684 return;
685 err:
686 error_msg("expecting 2 arguments (context and key)\n");
689 static void cmd_quit(char *arg)
691 quit();
694 static void cmd_reshuffle(char *arg)
696 editable_lock();
697 lib_reshuffle();
698 pl_reshuffle();
699 editable_unlock();
702 static void cmd_source(char *arg)
704 char *filename = expand_filename(arg);
706 if (source_file(filename) == -1)
707 error_msg("sourcing %s: %s", filename, strerror(errno));
708 free(filename);
711 static void cmd_colorscheme(char *arg)
713 char filename[512];
715 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
716 if (source_file(filename) == -1) {
717 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
718 if (source_file(filename) == -1)
719 error_msg("sourcing %s: %s", filename, strerror(errno));
724 * \" inside double-quotes becomes "
725 * \\ inside double-quotes becomes \
727 static char *parse_quoted(const char **strp)
729 const char *str = *strp;
730 const char *start;
731 char *ret, *dst;
733 str++;
734 start = str;
735 while (1) {
736 int c = *str++;
738 if (c == 0)
739 goto error;
740 if (c == '"')
741 break;
742 if (c == '\\') {
743 if (*str++ == 0)
744 goto error;
747 *strp = str;
748 ret = xnew(char, str - start);
749 str = start;
750 dst = ret;
751 while (1) {
752 int c = *str++;
754 if (c == '"')
755 break;
756 if (c == '\\') {
757 c = *str++;
758 if (c != '"' && c != '\\')
759 *dst++ = '\\';
761 *dst++ = c;
763 *dst = 0;
764 return ret;
765 error:
766 error_msg("`\"' expected");
767 return NULL;
770 static char *parse_escaped(const char **strp)
772 const char *str = *strp;
773 const char *start;
774 char *ret, *dst;
776 start = str;
777 while (1) {
778 int c = *str;
780 if (c == 0 || c == ' ' || c == '\'' || c == '"')
781 break;
783 str++;
784 if (c == '\\') {
785 c = *str;
786 if (c == 0)
787 break;
788 str++;
791 *strp = str;
792 ret = xnew(char, str - start + 1);
793 str = start;
794 dst = ret;
795 while (1) {
796 int c = *str;
798 if (c == 0 || c == ' ' || c == '\'' || c == '"')
799 break;
801 str++;
802 if (c == '\\') {
803 c = *str;
804 if (c == 0) {
805 *dst++ = '\\';
806 break;
808 str++;
810 *dst++ = c;
812 *dst = 0;
813 return ret;
816 static char *parse_one(const char **strp)
818 const char *str = *strp;
819 char *ret = NULL;
821 while (1) {
822 char *part;
823 int c = *str;
825 if (!c || c == ' ')
826 break;
827 if (c == '"') {
828 part = parse_quoted(&str);
829 if (part == NULL)
830 goto error;
831 } else if (c == '\'') {
832 /* backslashes are normal chars inside single-quotes */
833 const char *end;
835 str++;
836 end = strchr(str, '\'');
837 if (end == NULL)
838 goto sq_missing;
839 part = xstrndup(str, end - str);
840 str = end + 1;
841 } else {
842 part = parse_escaped(&str);
845 if (ret == NULL) {
846 ret = part;
847 } else {
848 char *tmp = xstrjoin(ret, part);
849 free(ret);
850 ret = tmp;
853 *strp = str;
854 return ret;
855 sq_missing:
856 error_msg("`'' expected");
857 error:
858 free(ret);
859 return NULL;
862 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
864 char **av = NULL;
865 int nr = 0;
866 int alloc = 0;
868 while (*cmd) {
869 char *arg;
871 /* there can't be spaces at start of command
872 * and there is at least one argument */
873 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
874 /* {} is replaced with file arguments */
875 if (*args_idx != -1)
876 goto only_once_please;
877 *args_idx = nr;
878 cmd += 2;
879 goto skip_spaces;
880 } else {
881 arg = parse_one(&cmd);
882 if (arg == NULL)
883 goto error;
886 if (nr == alloc) {
887 alloc = alloc ? alloc * 2 : 4;
888 av = xrenew(char *, av, alloc + 1);
890 av[nr++] = arg;
891 skip_spaces:
892 while (*cmd == ' ')
893 cmd++;
895 av[nr] = NULL;
896 *ac = nr;
897 return av;
898 only_once_please:
899 error_msg("{} can be used only once");
900 error:
901 while (nr > 0)
902 free(av[--nr]);
903 free(av);
904 return NULL;
907 static struct track_info **sel_tis;
908 static int sel_tis_alloc;
909 static int sel_tis_nr;
911 static int add_ti(void *data, struct track_info *ti)
913 if (sel_tis_nr == sel_tis_alloc) {
914 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
915 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
917 track_info_ref(ti);
918 sel_tis[sel_tis_nr++] = ti;
919 return 0;
922 static void cmd_run(char *arg)
924 char **av, **argv;
925 int ac, argc, i, run, files_idx = -1;
927 if (cur_view > QUEUE_VIEW) {
928 info_msg("Command execution is supported only in views 1-4");
929 return;
932 av = parse_cmd(arg, &files_idx, &ac);
933 if (av == NULL) {
934 return;
937 /* collect selected files (struct track_info) */
938 sel_tis = NULL;
939 sel_tis_alloc = 0;
940 sel_tis_nr = 0;
942 editable_lock();
943 switch (cur_view) {
944 case TREE_VIEW:
945 __tree_for_each_sel(add_ti, NULL, 0);
946 break;
947 case SORTED_VIEW:
948 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
949 break;
950 case PLAYLIST_VIEW:
951 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
952 break;
953 case QUEUE_VIEW:
954 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
955 break;
957 editable_unlock();
959 if (sel_tis_nr == 0) {
960 /* no files selected, do nothing */
961 free_str_array(av);
962 return;
964 sel_tis[sel_tis_nr] = NULL;
966 /* build argv */
967 argv = xnew(char *, ac + sel_tis_nr + 1);
968 argc = 0;
969 if (files_idx == -1) {
970 /* add selected files after rest of the args */
971 for (i = 0; i < ac; i++)
972 argv[argc++] = av[i];
973 for (i = 0; i < sel_tis_nr; i++)
974 argv[argc++] = sel_tis[i]->filename;
975 } else {
976 for (i = 0; i < files_idx; i++)
977 argv[argc++] = av[i];
978 for (i = 0; i < sel_tis_nr; i++)
979 argv[argc++] = sel_tis[i]->filename;
980 for (i = files_idx; i < ac; i++)
981 argv[argc++] = av[i];
983 argv[argc] = NULL;
985 for (i = 0; argv[i]; i++)
986 d_print("ARG: '%s'\n", argv[i]);
988 run = 1;
989 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
990 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
991 info_msg("Aborted");
992 run = 0;
995 if (run) {
996 int status;
998 if (spawn(argv, &status)) {
999 error_msg("executing %s: %s", argv[0], strerror(errno));
1000 } else {
1001 if (WIFEXITED(status)) {
1002 int rc = WEXITSTATUS(status);
1004 if (rc)
1005 error_msg("%s returned %d", argv[0], rc);
1007 if (WIFSIGNALED(status))
1008 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1010 switch (cur_view) {
1011 case TREE_VIEW:
1012 case SORTED_VIEW:
1013 /* this must be done before sel_tis are unreffed */
1014 free_str_array(av);
1015 free(argv);
1017 /* remove non-existed files, update tags for changed files */
1018 cmus_update_tis(sel_tis, sel_tis_nr);
1020 /* we don't own sel_tis anymore! */
1021 return;
1025 free_str_array(av);
1026 free(argv);
1027 for (i = 0; sel_tis[i]; i++)
1028 track_info_unref(sel_tis[i]);
1029 free(sel_tis);
1032 static int get_one_ti(void *data, struct track_info *ti)
1034 struct track_info **sel_ti = data;
1036 track_info_ref(ti);
1037 *sel_ti = ti;
1038 /* stop the for each loop, we need only the first selected track */
1039 return 1;
1042 static void cmd_echo(char *arg)
1044 struct track_info *sel_ti;
1045 char *ptr = arg;
1047 while (1) {
1048 ptr = strchr(ptr, '{');
1049 if (ptr == NULL)
1050 break;
1051 if (ptr[1] == '}')
1052 break;
1053 ptr++;
1056 if (ptr == NULL) {
1057 info_msg("%s", arg);
1058 return;
1061 if (cur_view > QUEUE_VIEW) {
1062 info_msg("echo with {} in its arguments is supported only in views 1-4");
1063 return;
1066 *ptr = 0;
1067 ptr += 2;
1069 /* get only the first selected track */
1070 sel_ti = NULL;
1072 editable_lock();
1073 switch (cur_view) {
1074 case TREE_VIEW:
1075 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1076 break;
1077 case SORTED_VIEW:
1078 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1079 break;
1080 case PLAYLIST_VIEW:
1081 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1082 break;
1083 case QUEUE_VIEW:
1084 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1085 break;
1087 editable_unlock();
1089 if (sel_ti == NULL)
1090 return;
1092 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1093 track_info_unref(sel_ti);
1096 #define VF_RELATIVE 0x01
1097 #define VF_PERCENTAGE 0x02
1099 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1101 unsigned int f = 0;
1102 int ch, val = 0, digits = 0, sign = 1;
1104 if (*arg == '-') {
1105 arg++;
1106 f |= VF_RELATIVE;
1107 sign = -1;
1108 } else if (*arg == '+') {
1109 arg++;
1110 f |= VF_RELATIVE;
1113 while (1) {
1114 ch = *arg++;
1115 if (ch < '0' || ch > '9')
1116 break;
1117 val *= 10;
1118 val += ch - '0';
1119 digits++;
1121 if (digits == 0)
1122 goto err;
1124 if (ch == '%') {
1125 f |= VF_PERCENTAGE;
1126 ch = *arg;
1128 if (ch)
1129 goto err;
1131 *value = sign * val;
1132 *flags = f;
1133 return 0;
1134 err:
1135 return -1;
1138 static int calc_vol(int val, int old, unsigned int flags)
1140 if (flags & VF_RELATIVE) {
1141 if (flags & VF_PERCENTAGE)
1142 val = scale_from_percentage(val, volume_max);
1143 val += old;
1144 } else if (flags & VF_PERCENTAGE) {
1145 val = scale_from_percentage(val, volume_max);
1147 return clamp(val, 0, volume_max);
1151 * :vol value [value]
1153 * where value is [-+]?[0-9]+%?
1155 static void cmd_vol(char *arg)
1157 char **values = get_words(arg);
1158 unsigned int lf, rf;
1159 int l, r, ol, or;
1161 if (values[1] && values[2])
1162 goto err;
1164 if (parse_vol_arg(values[0], &l, &lf))
1165 goto err;
1167 r = l;
1168 rf = lf;
1169 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1170 goto err;
1172 free_str_array(values);
1174 player_get_volume(&ol, &or);
1175 l = calc_vol(l, ol, lf);
1176 r = calc_vol(r, or, rf);
1177 player_set_volume(l, r);
1178 return;
1179 err:
1180 free_str_array(values);
1181 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1184 static void cmd_view(char *arg)
1186 int view;
1188 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view))
1189 set_view(view - 1);
1192 static void cmd_p_next(char *arg)
1194 cmus_next();
1197 static void cmd_p_pause(char *arg)
1199 player_pause();
1202 static void cmd_p_play(char *arg)
1204 if (arg) {
1205 player_play_file(arg);
1206 } else {
1207 player_play();
1211 static void cmd_p_prev(char *arg)
1213 cmus_prev();
1216 static void cmd_p_stop(char *arg)
1218 player_stop();
1221 static void cmd_search_next(char *arg)
1223 if (search_str) {
1224 if (!search_next(searchable, search_str, search_direction))
1225 search_not_found();
1229 static void cmd_search_prev(char *arg)
1231 if (search_str) {
1232 if (!search_next(searchable, search_str, !search_direction))
1233 search_not_found();
1237 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1239 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1242 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1244 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1247 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1249 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1252 static for_each_sel_ti_cb view_for_each_sel[4] = {
1253 tree_for_each_sel,
1254 sorted_for_each_sel,
1255 pl_for_each_sel,
1256 pq_for_each_sel
1259 /* wrapper for void lib_add_track(struct track_info *) etc. */
1260 static int wrapper_cb(void *data, struct track_info *ti)
1262 add_ti_cb add = data;
1264 add(ti);
1265 return 0;
1268 static void add_from_browser(add_ti_cb add, int job_type)
1270 char *sel = browser_get_sel();
1272 if (sel) {
1273 enum file_type ft;
1274 char *ret;
1276 ft = cmus_detect_ft(sel, &ret);
1277 if (ft != FILE_TYPE_INVALID) {
1278 cmus_add(add, ret, ft, job_type);
1279 window_down(browser_win, 1);
1281 free(ret);
1282 free(sel);
1286 static void cmd_win_add_l(char *arg)
1288 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1289 return;
1291 if (cur_view <= QUEUE_VIEW) {
1292 editable_lock();
1293 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1294 editable_unlock();
1295 } else if (cur_view == BROWSER_VIEW) {
1296 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1300 static void cmd_win_add_p(char *arg)
1302 /* could allow adding dups? */
1303 if (cur_view == PLAYLIST_VIEW)
1304 return;
1306 if (cur_view <= QUEUE_VIEW) {
1307 editable_lock();
1308 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1309 editable_unlock();
1310 } else if (cur_view == BROWSER_VIEW) {
1311 add_from_browser(pl_add_track, JOB_TYPE_PL);
1315 static void cmd_win_add_Q(char *arg)
1317 if (cur_view == QUEUE_VIEW)
1318 return;
1320 if (cur_view <= QUEUE_VIEW) {
1321 editable_lock();
1322 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1323 editable_unlock();
1324 } else if (cur_view == BROWSER_VIEW) {
1325 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1329 static void cmd_win_add_q(char *arg)
1331 if (cur_view == QUEUE_VIEW)
1332 return;
1334 if (cur_view <= QUEUE_VIEW) {
1335 editable_lock();
1336 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1337 editable_unlock();
1338 } else if (cur_view == BROWSER_VIEW) {
1339 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1343 static void cmd_win_activate(char *arg)
1345 struct track_info *info = NULL;
1347 editable_lock();
1348 switch (cur_view) {
1349 case TREE_VIEW:
1350 info = tree_set_selected();
1351 break;
1352 case SORTED_VIEW:
1353 info = sorted_set_selected();
1354 break;
1355 case PLAYLIST_VIEW:
1356 info = pl_set_selected();
1357 break;
1358 case QUEUE_VIEW:
1359 break;
1360 case BROWSER_VIEW:
1361 browser_enter();
1362 break;
1363 case FILTERS_VIEW:
1364 filters_activate();
1365 break;
1366 case HELP_VIEW:
1367 help_select();
1368 break;
1370 editable_unlock();
1372 if (info) {
1373 /* update lib/pl mode */
1374 if (cur_view < 2)
1375 play_library = 1;
1376 if (cur_view == 2)
1377 play_library = 0;
1379 player_play_file(info->filename);
1380 track_info_unref(info);
1384 static void cmd_win_mv_after(char *arg)
1386 editable_lock();
1387 switch (cur_view) {
1388 case TREE_VIEW:
1389 break;
1390 case SORTED_VIEW:
1391 editable_move_after(&lib_editable);
1392 break;
1393 case PLAYLIST_VIEW:
1394 editable_move_after(&pl_editable);
1395 break;
1396 case QUEUE_VIEW:
1397 editable_move_after(&pq_editable);
1398 break;
1399 case BROWSER_VIEW:
1400 break;
1401 case FILTERS_VIEW:
1402 break;
1403 case HELP_VIEW:
1404 break;
1406 editable_unlock();
1409 static void cmd_win_mv_before(char *arg)
1411 editable_lock();
1412 switch (cur_view) {
1413 case TREE_VIEW:
1414 break;
1415 case SORTED_VIEW:
1416 editable_move_before(&lib_editable);
1417 break;
1418 case PLAYLIST_VIEW:
1419 editable_move_before(&pl_editable);
1420 break;
1421 case QUEUE_VIEW:
1422 editable_move_before(&pq_editable);
1423 break;
1424 case BROWSER_VIEW:
1425 break;
1426 case FILTERS_VIEW:
1427 break;
1428 case HELP_VIEW:
1429 break;
1431 editable_unlock();
1434 static void cmd_win_remove(char *arg)
1436 editable_lock();
1437 switch (cur_view) {
1438 case TREE_VIEW:
1439 tree_remove_sel();
1440 break;
1441 case SORTED_VIEW:
1442 editable_remove_sel(&lib_editable);
1443 break;
1444 case PLAYLIST_VIEW:
1445 editable_remove_sel(&pl_editable);
1446 break;
1447 case QUEUE_VIEW:
1448 editable_remove_sel(&pq_editable);
1449 break;
1450 case BROWSER_VIEW:
1451 browser_delete();
1452 break;
1453 case FILTERS_VIEW:
1454 filters_delete_filter();
1455 break;
1456 case HELP_VIEW:
1457 help_remove();
1458 break;
1460 editable_unlock();
1463 static void cmd_win_sel_cur(char *arg)
1465 editable_lock();
1466 switch (cur_view) {
1467 case TREE_VIEW:
1468 tree_sel_current();
1469 break;
1470 case SORTED_VIEW:
1471 sorted_sel_current();
1472 break;
1473 case PLAYLIST_VIEW:
1474 pl_sel_current();
1475 break;
1476 case QUEUE_VIEW:
1477 break;
1478 case BROWSER_VIEW:
1479 break;
1480 case FILTERS_VIEW:
1481 break;
1482 case HELP_VIEW:
1483 break;
1485 editable_unlock();
1488 static void cmd_win_toggle(char *arg)
1490 switch (cur_view) {
1491 case TREE_VIEW:
1492 editable_lock();
1493 tree_toggle_expand_artist();
1494 editable_unlock();
1495 break;
1496 case SORTED_VIEW:
1497 editable_lock();
1498 editable_toggle_mark(&lib_editable);
1499 editable_unlock();
1500 break;
1501 case PLAYLIST_VIEW:
1502 editable_lock();
1503 editable_toggle_mark(&pl_editable);
1504 editable_unlock();
1505 break;
1506 case QUEUE_VIEW:
1507 editable_lock();
1508 editable_toggle_mark(&pq_editable);
1509 editable_unlock();
1510 break;
1511 case BROWSER_VIEW:
1512 break;
1513 case FILTERS_VIEW:
1514 filters_toggle_filter();
1515 break;
1516 case HELP_VIEW:
1517 help_toggle();
1518 break;
1522 static struct window *current_win(void)
1524 switch (cur_view) {
1525 case TREE_VIEW:
1526 return lib_cur_win;
1527 case SORTED_VIEW:
1528 return lib_editable.win;
1529 case PLAYLIST_VIEW:
1530 return pl_editable.win;
1531 case QUEUE_VIEW:
1532 return pq_editable.win;
1533 case BROWSER_VIEW:
1534 return browser_win;
1535 case HELP_VIEW:
1536 return help_win;
1537 case FILTERS_VIEW:
1538 default:
1539 return filters_win;
1543 static void cmd_win_bottom(char *arg)
1545 editable_lock();
1546 window_goto_bottom(current_win());
1547 editable_unlock();
1550 static void cmd_win_down(char *arg)
1552 editable_lock();
1553 window_down(current_win(), 1);
1554 editable_unlock();
1557 static void cmd_win_next(char *arg)
1559 if (cur_view == TREE_VIEW) {
1560 editable_lock();
1561 tree_toggle_active_window();
1562 editable_unlock();
1566 static void cmd_win_pg_down(char *arg)
1568 editable_lock();
1569 window_page_down(current_win());
1570 editable_unlock();
1573 static void cmd_win_pg_up(char *arg)
1575 editable_lock();
1576 window_page_up(current_win());
1577 editable_unlock();
1580 static void cmd_win_top(char *arg)
1582 editable_lock();
1583 window_goto_top(current_win());
1584 editable_unlock();
1587 static void cmd_win_up(char *arg)
1589 editable_lock();
1590 window_up(current_win(), 1);
1591 editable_unlock();
1594 static void cmd_win_update(char *arg)
1596 switch (cur_view) {
1597 case TREE_VIEW:
1598 case SORTED_VIEW:
1599 cmus_update_lib();
1600 break;
1601 case BROWSER_VIEW:
1602 browser_reload();
1603 break;
1607 static void cmd_browser_up(char *arg)
1609 browser_up();
1612 static void cmd_refresh(char *arg)
1614 clearok(curscr, TRUE);
1615 refresh();
1618 /* tab exp {{{
1620 * these functions fill tabexp struct, which is resetted beforehand
1623 /* buffer used for tab expansion */
1624 static char expbuf[512];
1626 static int filter_directories(const char *name, const struct stat *s)
1628 return S_ISDIR(s->st_mode);
1631 static int filter_any(const char *name, const struct stat *s)
1633 return 1;
1636 static int filter_playable(const char *name, const struct stat *s)
1638 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1641 static int filter_playlist(const char *name, const struct stat *s)
1643 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1646 static int filter_supported(const char *name, const struct stat *s)
1648 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1651 static void expand_files(const char *str)
1653 expand_files_and_dirs(str, filter_any);
1656 static void expand_directories(const char *str)
1658 expand_files_and_dirs(str, filter_directories);
1661 static void expand_playable(const char *str)
1663 expand_files_and_dirs(str, filter_playable);
1666 static void expand_playlist(const char *str)
1668 expand_files_and_dirs(str, filter_playlist);
1671 static void expand_supported(const char *str)
1673 expand_files_and_dirs(str, filter_supported);
1676 static void expand_add(const char *str)
1678 int flag = parse_flags(&str, "lpqQ");
1680 if (flag == -1)
1681 return;
1682 if (str == NULL)
1683 str = "";
1684 expand_supported(str);
1686 if (tabexp.head && flag) {
1687 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1688 free(tabexp.head);
1689 tabexp.head = xstrdup(expbuf);
1693 static void expand_load_save(const char *str)
1695 int flag = parse_flags(&str, "lp");
1697 if (flag == -1)
1698 return;
1699 if (str == NULL)
1700 str = "";
1701 expand_playlist(str);
1703 if (tabexp.head && flag) {
1704 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1705 free(tabexp.head);
1706 tabexp.head = xstrdup(expbuf);
1710 static void expand_key_context(const char *str, const char *force)
1712 int pos, i, len = strlen(str);
1713 char **tails;
1715 tails = xnew(char *, NR_CTXS + 1);
1716 pos = 0;
1717 for (i = 0; key_context_names[i]; i++) {
1718 int cmp = strncmp(str, key_context_names[i], len);
1719 if (cmp > 0)
1720 continue;
1721 if (cmp < 0)
1722 break;
1723 tails[pos++] = xstrdup(key_context_names[i] + len);
1726 if (pos == 0) {
1727 free(tails);
1728 return;
1730 if (pos == 1) {
1731 char *tmp = xstrjoin(tails[0], " ");
1732 free(tails[0]);
1733 tails[0] = tmp;
1735 tails[pos] = NULL;
1736 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1737 tabexp.head = xstrdup(expbuf);
1738 tabexp.tails = tails;
1739 tabexp.nr_tails = pos;
1742 static int get_context(const char *str, int len)
1744 int i, c = -1, count = 0;
1746 for (i = 0; key_context_names[i]; i++) {
1747 if (strncmp(str, key_context_names[i], len) == 0) {
1748 if (key_context_names[i][len] == 0) {
1749 /* exact */
1750 return i;
1752 c = i;
1753 count++;
1756 if (count == 1)
1757 return c;
1758 return -1;
1761 static void expand_command_line(const char *str);
1763 static void expand_bind_args(const char *str)
1765 /* :bind context key function
1767 * possible values for str:
1769 * context k
1770 * context key f
1772 * you need to know context before you can expand function
1774 /* start and end pointers for context, key and function */
1775 const char *cs, *ce, *ks, *ke, *fs;
1776 int i, c, k, count;
1777 int flag = parse_flags((const char **)&str, "f");
1778 const char *force = "";
1780 if (flag == -1)
1781 return;
1782 if (str == NULL)
1783 str = "";
1785 if (flag == 'f')
1786 force = "-f ";
1788 cs = str;
1789 ce = strchr(cs, ' ');
1790 if (ce == NULL) {
1791 expand_key_context(cs, force);
1792 return;
1795 /* context must be expandable */
1796 c = get_context(cs, ce - cs);
1797 if (c == -1) {
1798 /* context is ambiguous or invalid */
1799 return;
1802 ks = ce;
1803 while (*ks == ' ')
1804 ks++;
1805 ke = strchr(ks, ' ');
1806 if (ke == NULL) {
1807 /* expand key */
1808 int len = strlen(ks);
1809 PTR_ARRAY(array);
1811 for (i = 0; key_table[i].name; i++) {
1812 int cmp = strncmp(ks, key_table[i].name, len);
1813 if (cmp > 0)
1814 continue;
1815 if (cmp < 0)
1816 break;
1817 ptr_array_add(&array, xstrdup(key_table[i].name + len));
1820 if (!array.count)
1821 return;
1823 if (array.count == 1) {
1824 char **ptrs = array.ptrs;
1825 char *tmp = xstrjoin(ptrs[0], " ");
1826 free(ptrs[0]);
1827 ptrs[0] = tmp;
1830 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
1832 ptr_array_plug(&array);
1833 tabexp.head = xstrdup(expbuf);
1834 tabexp.tails = array.ptrs;
1835 tabexp.nr_tails = array.count;
1836 return;
1839 /* key must be expandable */
1840 k = -1;
1841 count = 0;
1842 for (i = 0; key_table[i].name; i++) {
1843 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
1844 if (key_table[i].name[ke - ks] == 0) {
1845 /* exact */
1846 k = i;
1847 count = 1;
1848 break;
1850 k = i;
1851 count++;
1854 if (count != 1) {
1855 /* key is ambiguous or invalid */
1856 return;
1859 fs = ke;
1860 while (*fs == ' ')
1861 fs++;
1863 if (*fs == ':')
1864 fs++;
1866 /* expand com [arg...] */
1867 expand_command_line(fs);
1868 if (tabexp.head == NULL) {
1869 /* command expand failed */
1870 return;
1874 * tabexp.head is now "com"
1875 * tabexp.tails is [ mand1 mand2 ... ]
1877 * need to change tabexp.head to "context key com"
1880 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
1881 key_table[k].name, tabexp.head);
1882 free(tabexp.head);
1883 tabexp.head = xstrdup(expbuf);
1886 static void expand_unbind_args(const char *str)
1888 /* :unbind context key */
1889 /* start and end pointers for context and key */
1890 const char *cs, *ce, *ks;
1891 const struct binding *b;
1892 PTR_ARRAY(array);
1893 int c, len;
1895 cs = str;
1896 ce = strchr(cs, ' ');
1897 if (ce == NULL) {
1898 expand_key_context(cs, "");
1899 return;
1902 /* context must be expandable */
1903 c = get_context(cs, ce - cs);
1904 if (c == -1) {
1905 /* context is ambiguous or invalid */
1906 return;
1909 ks = ce;
1910 while (*ks == ' ')
1911 ks++;
1913 /* expand key */
1914 len = strlen(ks);
1915 b = key_bindings[c];
1916 while (b) {
1917 if (!strncmp(ks, b->key->name, len))
1918 ptr_array_add(&array, xstrdup(b->key->name + len));
1919 b = b->next;
1921 if (!array.count)
1922 return;
1924 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
1926 ptr_array_plug(&array);
1927 tabexp.head = xstrdup(expbuf);
1928 tabexp.tails = array.ptrs;
1929 tabexp.nr_tails = array.count;
1932 static void expand_factivate(const char *str)
1934 /* "name1 name2 name3", expand only name3 */
1935 struct filter_entry *e;
1936 const char *name;
1937 PTR_ARRAY(array);
1938 int str_len, len, i;
1940 str_len = strlen(str);
1941 i = str_len;
1942 while (i > 0) {
1943 if (str[i - 1] == ' ')
1944 break;
1945 i--;
1947 len = str_len - i;
1948 name = str + i;
1950 list_for_each_entry(e, &filters_head, node) {
1951 if (!strncmp(name, e->name, len))
1952 ptr_array_add(&array, xstrdup(e->name + len));
1954 if (!array.count)
1955 return;
1957 ptr_array_plug(&array);
1958 tabexp.head = xstrdup(str);
1959 tabexp.tails = array.ptrs;
1960 tabexp.nr_tails = array.count;
1963 static void expand_options(const char *str)
1965 struct cmus_opt *opt;
1966 int len;
1967 char **tails;
1969 /* tabexp is resetted */
1970 len = strlen(str);
1971 if (len > 1 && str[len - 1] == '=') {
1972 /* expand value */
1973 char *var = xstrndup(str, len - 1);
1975 list_for_each_entry(opt, &option_head, node) {
1976 if (strcmp(var, opt->name) == 0) {
1977 char buf[OPTION_MAX_SIZE];
1979 tails = xnew(char *, 2);
1981 buf[0] = 0;
1982 opt->get(opt->id, buf);
1983 tails[0] = xstrdup(buf);
1984 tails[1] = NULL;
1986 tabexp.head = xstrdup(str);
1987 tabexp.tails = tails;
1988 tabexp.nr_tails = 1;
1989 free(var);
1990 return;
1993 free(var);
1994 } else {
1995 /* expand variable */
1996 int pos;
1998 tails = xnew(char *, nr_options + 1);
1999 pos = 0;
2000 list_for_each_entry(opt, &option_head, node) {
2001 if (strncmp(str, opt->name, len) == 0)
2002 tails[pos++] = xstrdup(opt->name + len);
2004 if (pos > 0) {
2005 if (pos == 1) {
2006 /* only one variable matches, add '=' */
2007 char *tmp = xstrjoin(tails[0], "=");
2009 free(tails[0]);
2010 tails[0] = tmp;
2013 tails[pos] = NULL;
2014 tabexp.head = xstrdup(str);
2015 tabexp.tails = tails;
2016 tabexp.nr_tails = pos;
2017 } else {
2018 free(tails);
2023 static void expand_toptions(const char *str)
2025 struct cmus_opt *opt;
2026 int len, pos;
2027 char **tails;
2029 tails = xnew(char *, nr_options + 1);
2030 len = strlen(str);
2031 pos = 0;
2032 list_for_each_entry(opt, &option_head, node) {
2033 if (opt->toggle == NULL)
2034 continue;
2035 if (strncmp(str, opt->name, len) == 0)
2036 tails[pos++] = xstrdup(opt->name + len);
2038 if (pos > 0) {
2039 tails[pos] = NULL;
2040 tabexp.head = xstrdup(str);
2041 tabexp.tails = tails;
2042 tabexp.nr_tails = pos;
2043 } else {
2044 free(tails);
2048 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2050 struct directory dir;
2051 const char *name, *dot;
2052 int len = strlen(str);
2054 if (dir_open(&dir, dirname))
2055 return;
2057 while ((name = dir_read(&dir))) {
2058 if (!S_ISREG(dir.st.st_mode))
2059 continue;
2060 if (strncmp(name, str, len))
2061 continue;
2062 dot = strrchr(name, '.');
2063 if (dot == NULL || strcmp(dot, ".theme"))
2064 continue;
2065 if (dot - name < len)
2066 /* str is "foo.th"
2067 * matches "foo.theme"
2068 * which also ends with ".theme"
2070 continue;
2071 ptr_array_add(array, xstrndup(name + len, dot - name - len));
2073 dir_close(&dir);
2076 static void expand_colorscheme(const char *str)
2078 PTR_ARRAY(array);
2080 load_themes(cmus_config_dir, str, &array);
2081 load_themes(DATADIR "/cmus", str, &array);
2083 if (array.count) {
2084 ptr_array_sort(&array, strptrcmp);
2086 ptr_array_plug(&array);
2087 tabexp.head = xstrdup(str);
2088 tabexp.tails = array.ptrs;
2089 tabexp.nr_tails = array.count;
2093 /* tab exp }}} */
2095 /* sort by name */
2096 struct command commands[] = {
2097 { "add", cmd_add, 1, 1, expand_add, 0, 0 },
2098 { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
2099 { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
2100 { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
2101 { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
2102 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
2103 { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
2104 { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
2105 { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
2106 { "fset", cmd_fset, 1, 1, NULL, 0, 0 },
2107 { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
2108 { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
2109 { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
2110 { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
2111 { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
2112 { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
2113 { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
2114 { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
2115 { "quit", cmd_quit, 0, 0, NULL, 0, 0 },
2116 { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
2117 { "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
2118 { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
2119 { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
2120 { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
2121 { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
2122 { "set", cmd_set, 1, 1, expand_options, 0, 0 },
2123 { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
2124 { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
2125 { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
2126 { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
2127 { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
2128 { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
2129 { "view", cmd_view, 1, 1, NULL, 0, 0 },
2130 { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
2131 { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
2132 { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
2133 { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
2134 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
2135 { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
2136 { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
2137 { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
2138 { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
2139 { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
2140 { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
2141 { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
2142 { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
2143 { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
2144 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
2145 { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
2146 { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
2147 { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
2148 { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
2149 { NULL, NULL, 0, 0, 0, 0, 0 }
2152 /* fills tabexp struct */
2153 static void expand_commands(const char *str)
2155 int i, len, pos;
2156 char **tails;
2158 /* tabexp is resetted */
2159 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2160 len = strlen(str);
2161 pos = 0;
2162 for (i = 0; commands[i].name; i++) {
2163 if (strncmp(str, commands[i].name, len) == 0)
2164 tails[pos++] = xstrdup(commands[i].name + len);
2166 if (pos > 0) {
2167 if (pos == 1) {
2168 /* only one command matches, add ' ' */
2169 char *tmp = xstrjoin(tails[0], " ");
2171 free(tails[0]);
2172 tails[0] = tmp;
2174 tails[pos] = NULL;
2175 tabexp.head = xstrdup(str);
2176 tabexp.tails = tails;
2177 tabexp.nr_tails = pos;
2178 } else {
2179 free(tails);
2183 struct command *get_command(const char *str)
2185 int i, len;
2187 while (*str == ' ')
2188 str++;
2189 for (len = 0; str[len] && str[len] != ' '; len++)
2192 for (i = 0; commands[i].name; i++) {
2193 if (strncmp(str, commands[i].name, len))
2194 continue;
2196 if (commands[i].name[len] == 0) {
2197 /* exact */
2198 return &commands[i];
2201 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2202 /* ambiguous */
2203 return NULL;
2205 return &commands[i];
2207 return NULL;
2210 /* fills tabexp struct */
2211 static void expand_command_line(const char *str)
2213 /* :command [arg]...
2215 * examples:
2217 * str expanded value (tabexp.head)
2218 * -------------------------------------
2219 * fs fset
2220 * b c bind common
2221 * se se (tabexp.tails = [ ek t ])
2223 /* command start/end, argument start */
2224 const char *cs, *ce, *as;
2225 const struct command *cmd;
2227 cs = str;
2228 ce = strchr(cs, ' ');
2229 if (ce == NULL) {
2230 /* expand command */
2231 expand_commands(cs);
2232 return;
2235 /* command must be expandable */
2236 cmd = get_command(cs);
2237 if (cmd == NULL) {
2238 /* command ambiguous or invalid */
2239 return;
2242 if (cmd->expand == NULL) {
2243 /* can't expand argument */
2244 return;
2247 as = ce;
2248 while (*as == ' ')
2249 as++;
2251 /* expand argument */
2252 cmd->expand(as);
2253 if (tabexp.head == NULL) {
2254 /* argument expansion failed */
2255 return;
2258 /* tabexp.head is now start of the argument string */
2259 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2260 free(tabexp.head);
2261 tabexp.head = xstrdup(expbuf);
2264 static void tab_expand(void)
2266 char *s1, *s2, *tmp;
2267 int pos;
2269 /* strip white space */
2270 pos = 0;
2271 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2272 pos++;
2274 /* string to expand */
2275 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2277 /* tail */
2278 s2 = xstrdup(cmdline.line + cmdline.bpos);
2280 tmp = tabexp_expand(s1, expand_command_line);
2281 if (tmp) {
2282 /* tmp.s2 */
2283 int l1, l2;
2285 l1 = strlen(tmp);
2286 l2 = strlen(s2);
2287 cmdline.blen = l1 + l2;
2288 if (cmdline.blen >= cmdline.size) {
2289 while (cmdline.blen >= cmdline.size)
2290 cmdline.size *= 2;
2291 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2293 sprintf(cmdline.line, "%s%s", tmp, s2);
2294 cmdline.bpos = l1;
2295 cmdline.cpos = u_strlen(tmp);
2296 cmdline.clen = u_strlen(cmdline.line);
2297 free(tmp);
2299 free(s1);
2300 free(s2);
2303 static void reset_tab_expansion(void)
2305 tabexp_reset();
2306 arg_expand_cmd = -1;
2309 int run_only_safe_commands;
2311 /* FIXME: parse all arguments */
2312 void run_command(const char *buf)
2314 char *cmd, *arg;
2315 int cmd_start, cmd_end, cmd_len;
2316 int arg_start, arg_end;
2317 int i;
2319 i = 0;
2320 while (buf[i] && buf[i] == ' ')
2321 i++;
2323 if (buf[i] == '#')
2324 return;
2326 cmd_start = i;
2327 while (buf[i] && buf[i] != ' ')
2328 i++;
2329 cmd_end = i;
2330 while (buf[i] && buf[i] == ' ')
2331 i++;
2332 arg_start = i;
2333 while (buf[i])
2334 i++;
2335 arg_end = i;
2337 cmd_len = cmd_end - cmd_start;
2338 if (cmd_len == 0)
2339 return;
2341 cmd = xstrndup(buf + cmd_start, cmd_len);
2342 if (arg_start == arg_end) {
2343 arg = NULL;
2344 } else {
2345 arg = xstrndup(buf + arg_start, arg_end - arg_start);
2347 i = 0;
2348 while (1) {
2349 const struct command *c = &commands[i];
2351 if (c->name == NULL) {
2352 error_msg("unknown command\n");
2353 break;
2355 if (strncmp(cmd, c->name, cmd_len) == 0) {
2356 const char *next = commands[i + 1].name;
2357 int exact = c->name[cmd_len] == 0;
2359 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2360 error_msg("ambiguous command\n");
2361 break;
2363 if (c->min_args > 0 && arg == NULL) {
2364 error_msg("not enough arguments\n");
2365 break;
2367 if (c->max_args == 0 && arg) {
2368 error_msg("too many arguments\n");
2369 break;
2371 if (run_only_safe_commands && c->flags & CMD_UNSAFE) {
2372 d_print("trying to execute unsafe command over net\n");
2373 break;
2375 c->func(arg);
2376 break;
2378 i++;
2380 free(arg);
2381 free(cmd);
2384 static void reset_history_search(void)
2386 history_reset_search(&cmd_history);
2387 free(history_search_text);
2388 history_search_text = NULL;
2391 static void backspace(void)
2393 if (cmdline.clen > 0) {
2394 cmdline_backspace();
2395 } else {
2396 input_mode = NORMAL_MODE;
2400 void command_mode_ch(uchar ch)
2402 switch (ch) {
2403 case 0x01: // ^A
2404 cmdline_move_home();
2405 break;
2406 case 0x02: // ^B
2407 cmdline_move_left();
2408 break;
2409 case 0x04: // ^D
2410 cmdline_delete_ch();
2411 break;
2412 case 0x05: // ^E
2413 cmdline_move_end();
2414 break;
2415 case 0x06: // ^F
2416 cmdline_move_right();
2417 break;
2418 case 0x03: // ^C
2419 case 0x07: // ^G
2420 case 0x1B: // ESC
2421 if (cmdline.blen) {
2422 history_add_line(&cmd_history, cmdline.line);
2423 cmdline_clear();
2425 input_mode = NORMAL_MODE;
2426 break;
2427 case 0x0A:
2428 if (cmdline.blen) {
2429 run_command(cmdline.line);
2430 history_add_line(&cmd_history, cmdline.line);
2431 cmdline_clear();
2433 input_mode = NORMAL_MODE;
2434 break;
2435 case 0x0B:
2436 cmdline_clear_end();
2437 break;
2438 case 0x09:
2439 /* tab expansion should not complain */
2440 display_errors = 0;
2442 tab_expand();
2443 break;
2444 case 0x15:
2445 cmdline_backspace_to_bol();
2446 break;
2447 case 127:
2448 backspace();
2449 break;
2450 default:
2451 cmdline_insert_ch(ch);
2453 reset_history_search();
2454 if (ch != 0x09)
2455 reset_tab_expansion();
2458 void command_mode_key(int key)
2460 reset_tab_expansion();
2461 switch (key) {
2462 case KEY_DC:
2463 cmdline_delete_ch();
2464 break;
2465 case KEY_BACKSPACE:
2466 backspace();
2467 break;
2468 case KEY_LEFT:
2469 cmdline_move_left();
2470 return;
2471 case KEY_RIGHT:
2472 cmdline_move_right();
2473 return;
2474 case KEY_HOME:
2475 cmdline_move_home();
2476 return;
2477 case KEY_END:
2478 cmdline_move_end();
2479 return;
2480 case KEY_UP:
2482 const char *s;
2484 if (history_search_text == NULL)
2485 history_search_text = xstrdup(cmdline.line);
2486 s = history_search_forward(&cmd_history, history_search_text);
2487 if (s)
2488 cmdline_set_text(s);
2490 return;
2491 case KEY_DOWN:
2492 if (history_search_text) {
2493 const char *s;
2495 s = history_search_backward(&cmd_history, history_search_text);
2496 if (s) {
2497 cmdline_set_text(s);
2498 } else {
2499 cmdline_set_text(history_search_text);
2502 return;
2503 default:
2504 d_print("key = %c (%d)\n", key, key);
2506 reset_history_search();
2509 void commands_init(void)
2511 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2512 history_load(&cmd_history, cmd_history_filename, 2000);
2515 void commands_exit(void)
2517 history_save(&cmd_history);
2518 free(cmd_history_filename);
2519 tabexp_reset();