/* * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #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 "format_print.h" #include "spawn.h" #include "utils.h" #include "list.h" #include "debug.h" #include "load_dir.h" #include "config/datadir.h" #include "help.h" #include #include #include #include #include #include #if defined(__sun__) #include #else #include #endif static struct history cmd_history; static char *cmd_history_filename; static char *history_search_text = NULL; static int arg_expand_cmd = -1; static int prev_view = -1; static char *get_home_dir(const char *username) { struct passwd *passwd; if (username == NULL) return xstrdup(home_dir); passwd = getpwnam(username); if (passwd == NULL) return NULL; /* don't free passwd */ return xstrdup(passwd->pw_dir); } static char *expand_filename(const char *name) { if (name[0] == '~') { char *slash; slash = strchr(name, '/'); if (slash) { char *username, *home; if (slash - name - 1 > 0) { /* ~user/... */ username = xstrndup(name + 1, slash - name - 1); } else { /* ~/... */ username = NULL; } home = get_home_dir(username); free(username); if (home) { char *expanded; expanded = xstrjoin(home, slash); free(home); return expanded; } else { return xstrdup(name); } } else { if (name[1] == 0) { return xstrdup(home_dir); } else { char *home; home = get_home_dir(name + 1); if (home) return home; return xstrdup(name); } } } else { return xstrdup(name); } } /* view {{{ */ void view_clear(int view) { switch (view) { case TREE_VIEW: case SORTED_VIEW: worker_remove_jobs(JOB_TYPE_LIB); editable_lock(); editable_clear(&lib_editable); /* FIXME: make this optional? */ lib_clear_store(); editable_unlock(); break; case PLAYLIST_VIEW: worker_remove_jobs(JOB_TYPE_PL); editable_lock(); editable_clear(&pl_editable); editable_unlock(); break; case QUEUE_VIEW: worker_remove_jobs(JOB_TYPE_QUEUE); editable_lock(); editable_clear(&pq_editable); editable_unlock(); 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); break; case PLAYLIST_VIEW: cmus_add(pl_add_track, name, ft, JOB_TYPE_PL); break; case QUEUE_VIEW: if (prepend) { cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE); } else { cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE); } break; default: info_msg(":add only works in views 1-4"); } free(name); } void view_load(int view, char *arg) { char *tmp, *name; enum file_type ft; tmp = expand_filename(arg); ft = cmus_detect_ft(tmp, &name); if (ft == FILE_TYPE_INVALID) { error_msg("loading '%s': %s", tmp, strerror(errno)); free(tmp); return; } 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; } switch (view) { case TREE_VIEW: case SORTED_VIEW: worker_remove_jobs(JOB_TYPE_LIB); editable_lock(); editable_clear(&lib_editable); editable_unlock(); cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB); free(lib_filename); lib_filename = name; break; case PLAYLIST_VIEW: worker_remove_jobs(JOB_TYPE_PL); editable_lock(); editable_clear(&pl_editable); editable_unlock(); cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL); free(pl_filename); pl_filename = name; break; default: info_msg(":load only works in views 1-3"); free(name); } } static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep) { char *filename = *filenamep; if (arg) { free(filename); filename = xstrdup(arg); *filenamep = filename; } editable_lock(); if (cmus_save(for_each_ti, filename) == -1) error_msg("saving '%s': %s", filename, strerror(errno)); editable_unlock(); } void view_save(int view, char *arg) { if (arg) { char *tmp; tmp = expand_filename(arg); arg = path_absolute(tmp); free(tmp); } switch (view) { case TREE_VIEW: case SORTED_VIEW: if (worker_has_job(JOB_TYPE_LIB)) goto worker_running; do_save(lib_for_each, arg, &lib_filename); break; case PLAYLIST_VIEW: if (worker_has_job(JOB_TYPE_PL)) goto worker_running; do_save(pl_for_each, arg, &pl_filename); break; default: info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)"); } free(arg); return; worker_running: error_msg("can't save when tracks are being added"); free(arg); } /* }}} */ /* only returns the last flag which is enough for it's callers */ static int parse_flags(const char **strp, const char *flags) { const char *str = *strp; int flag = 0; if (str == NULL) return flag; while (*str) { 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 flag_to_view(int flag) { switch (flag) { case 'l': return TREE_VIEW; case 'p': return PLAYLIST_VIEW; case 'q': case 'Q': return QUEUE_VIEW; default: return cur_view; } } 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 flag = parse_flags((const char **)&arg, "lp"); if (flag == -1) return; view_save(flag_to_view(flag), arg); } 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; } else { struct cmus_opt *opt; char buf[OPTION_MAX_SIZE]; /* support "set