/* * Copyright 2008-2013 Various Authors * Copyright 2004-2005 Timo Hirvonen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "command_mode.h" #include "search_mode.h" #include "cmdline.h" #include "options.h" #include "ui_curses.h" #include "history.h" #include "tabexp.h" #include "tabexp_file.h" #include "browser.h" #include "filters.h" #include "player.h" #include "output.h" #include "editable.h" #include "lib.h" #include "pl.h" #include "play_queue.h" #include "cmus.h" #include "worker.h" #include "keys.h" #include "xmalloc.h" #include "xstrjoin.h" #include "misc.h" #include "path.h" #include "spawn.h" #include "utils.h" #include "list.h" #include "debug.h" #include "load_dir.h" #include "help.h" #include "op.h" #include "mpris.h" #include "job.h" #include #include #include #include #include static struct history cmd_history; static char *cmd_history_filename; static char *history_search_text = NULL; static int arg_expand_cmd = -1; static int mute_vol_l = 0, mute_vol_r = 0; /* view {{{ */ void view_clear(int view) { switch (view) { case TREE_VIEW: case SORTED_VIEW: worker_remove_jobs_by_type(JOB_TYPE_LIB); editable_clear(&lib_editable); /* FIXME: make this optional? */ lib_clear_store(); break; case PLAYLIST_VIEW: pl_clear(); break; case QUEUE_VIEW: worker_remove_jobs_by_type(JOB_TYPE_QUEUE); editable_clear(&pq_editable); break; default: info_msg(":clear only works in views 1-4"); } } void view_add(int view, char *arg, int prepend) { char *tmp, *name; enum file_type ft; tmp = expand_filename(arg); ft = cmus_detect_ft(tmp, &name); if (ft == FILE_TYPE_INVALID) { error_msg("adding '%s': %s", tmp, strerror(errno)); free(tmp); return; } free(tmp); switch (view) { case TREE_VIEW: case SORTED_VIEW: cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB, 0, NULL); break; case PLAYLIST_VIEW: pl_add_file_to_marked_pl(name); break; case QUEUE_VIEW: if (prepend) { cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE, 0, NULL); } else { cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE, 0, NULL); } break; default: info_msg(":add only works in views 1-4"); } free(name); } static char *view_load_prepare(char *arg) { char *name, *tmp = expand_filename(arg); enum file_type ft = cmus_detect_ft(tmp, &name); if (ft == FILE_TYPE_INVALID) { error_msg("loading '%s': %s", tmp, strerror(errno)); free(tmp); return NULL; } free(tmp); if (ft == FILE_TYPE_FILE) ft = FILE_TYPE_PL; if (ft != FILE_TYPE_PL) { error_msg("loading '%s': not a playlist file", name); free(name); return NULL; } return name; } void view_load(int view, char *arg) { char *name = view_load_prepare(arg); if (!name) return; switch (view) { case TREE_VIEW: case SORTED_VIEW: worker_remove_jobs_by_type(JOB_TYPE_LIB); editable_clear(&lib_editable); cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB, 0, NULL); free(lib_filename); lib_filename = name; break; default: info_msg(":load only works in views 1-2"); free(name); } } static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep, save_ti_cb save_ti) { char *filename = *filenamep; if (arg) { if (strcmp(arg, "-") == 0) { filename = (char *) arg; } else { free(filename); filename = xstrdup(arg); *filenamep = filename; } } else if (!filename) { error_msg("need a file as argument, no default stored yet"); return; } if (save_ti(for_each_ti, filename, NULL) == -1) error_msg("saving '%s': %s", filename, strerror(errno)); } void view_save(int view, char *arg, int to_stdout, int filtered, int extended) { char **dest; save_ti_cb save_ti = extended ? cmus_save_ext : cmus_save; for_each_ti_cb lib_for_each_ti = filtered ? lib_for_each_filtered : lib_for_each; if (arg) { if (to_stdout) { arg = xstrdup(arg); } else { char *tmp = expand_filename(arg); arg = path_absolute(tmp); free(tmp); } } switch (view) { case TREE_VIEW: case SORTED_VIEW: if (worker_has_job_by_type(JOB_TYPE_LIB)) goto worker_running; dest = extended ? &lib_ext_filename : &lib_filename; do_save(lib_for_each_ti, arg, dest, save_ti); break; case PLAYLIST_VIEW: if (arg) pl_export_selected_pl(arg); else pl_save(); break; case QUEUE_VIEW: if (worker_has_job_by_type(JOB_TYPE_QUEUE)) goto worker_running; dest = extended ? &play_queue_ext_filename : &play_queue_filename; do_save(play_queue_for_each, arg, dest, save_ti); break; default: info_msg(":save only works in views 1 - 4"); } free(arg); return; worker_running: error_msg("can't save when tracks are being added"); free(arg); } /* }}} */ /* if only_last != 0, only return the last flag */ static int do_parse_flags(const char **strp, const char *flags, int only_last) { const char *str = *strp; int flag = 0; if (str == NULL) return flag; while (*str && (only_last || !flag)) { if (*str != '-') break; // "-" if (str[1] == 0) break; // "--" or "-- " if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) { str += 2; break; } // not "-?" or "-? " if (str[2] && str[2] != ' ') break; flag = str[1]; if (!strchr(flags, flag)) { error_msg("invalid option -%c", flag); return -1; } str += 2; while (*str == ' ') str++; } while (*str == ' ') str++; if (*str == 0) str = NULL; *strp = str; return flag; } static int parse_flags(const char **strp, const char *flags) { return do_parse_flags(strp, flags, 1); } static int parse_one_flag(const char **strp, const char *flags) { return do_parse_flags(strp, flags, 0); } /* is str == "...-", but not "...-- -" ? copied from do_parse_flags() */ static int is_stdout_filename(const char *str) { if (!str) return 0; while (*str) { if (*str != '-') return 0; // "-" if (str[1] == 0) return 1; // "--" or "-- " if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) return 0; // not "-?" or "-? " if (str[2] && str[2] != ' ') return 0; str += 2; while (*str == ' ') str++; } return 0; } static int flag_to_view(int flag) { switch (flag) { case 'l': case 'L': return TREE_VIEW; case 'p': return PLAYLIST_VIEW; case 'q': case 'Q': return QUEUE_VIEW; default: return cur_view; } } struct window *current_win(void) { switch (cur_view) { case TREE_VIEW: return lib_cur_win; case SORTED_VIEW: return lib_editable.shared->win; case PLAYLIST_VIEW: return pl_cursor_win(); case QUEUE_VIEW: return pq_editable.shared->win; case BROWSER_VIEW: return browser_win; case HELP_VIEW: return help_win; case FILTERS_VIEW: default: return filters_win; } } static void cmd_add(char *arg) { int flag = parse_flags((const char **)&arg, "lpqQ"); if (flag == -1) return; if (arg == NULL) { error_msg("not enough arguments\n"); return; } view_add(flag_to_view(flag), arg, flag == 'Q'); } static void cmd_clear(char *arg) { int flag = parse_flags((const char **)&arg, "lpq"); if (flag == -1) return; if (arg) { error_msg("too many arguments\n"); return; } view_clear(flag_to_view(flag)); } static void cmd_load(char *arg) { int flag = parse_flags((const char **)&arg, "lp"); if (flag == -1) return; if (arg == NULL) { error_msg("not enough arguments\n"); return; } view_load(flag_to_view(flag), arg); } static void cmd_save(char *arg) { int to_stdout = is_stdout_filename(arg); int flag = 0, f, extended = 0; do { f = parse_one_flag((const char **)&arg, "eLlpq"); if (f == 'e') extended = 1; else if (f) flag = f; } while (f > 0); if (flag == -1) return; view_save(flag_to_view(flag), arg, to_stdout, flag == 'L', extended); } static void cmd_set(char *arg) { char *value = NULL; int i; for (i = 0; arg[i]; i++) { if (arg[i] == '=') { arg[i] = 0; value = &arg[i + 1]; break; } } if (value) { option_set(arg, value); help_win->changed = 1; if (cur_view == TREE_VIEW) { lib_track_win->changed = 1; lib_tree_win->changed = 1; } else if (cur_view == PLAYLIST_VIEW) { pl_mark_for_redraw(); } else { current_win()->changed = 1; } update_titleline(); update_statusline(); } else { struct cmus_opt *opt; char buf[OPTION_MAX_SIZE]; /* support "set